diff --git a/.clang-tidy b/.clang-tidy index 37a8aa66a1..8ed34ebb0f 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -63,6 +63,14 @@ # -llvmlibc-inline-function-decl # -readability-avoid-unconditional-preprocessor-if # +# TODO Code Quality WORKAROUND ROCm 6.1 +# -cppcoreguidelines-misleading-capture-default-by-value +# -cppcoreguidelines-missing-std-forward +# -cppcoreguidelines-use-default-member-init +# -misc-include-cleaner +# -modernize-type-traits +# -performance-avoid-endl +# Checks: >- *, -abseil-*, @@ -93,6 +101,8 @@ Checks: >- -cppcoreguidelines-explicit-virtual-functions, -cppcoreguidelines-init-variables, -cppcoreguidelines-macro-usage, + -cppcoreguidelines-misleading-capture-default-by-value, + -cppcoreguidelines-missing-std-forward, -cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-prefer-member-initializer, @@ -105,6 +115,7 @@ Checks: >- -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-rvalue-reference-param-not-moved, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-default-member-init, -fuchsia-*, -google-explicit-constructor, -google-readability-casting, @@ -136,6 +147,7 @@ Checks: >- -llvm-qualified-auto, -misc-confusable-identifiers, -misc-const-correctness, + -misc-include-cleaner, -misc-misplaced-const, -misc-non-private-member-variables-in-classes, -misc-no-recursion, @@ -145,6 +157,7 @@ Checks: >- -modernize-deprecated-headers, -modernize-macro-to-enum, -modernize-pass-by-value, + -modernize-type-traits, -modernize-use-auto, -modernize-use-default-member-init, -modernize-use-emplace, @@ -154,6 +167,7 @@ Checks: >- -modernize-use-nodiscard, -modernize-concat-nested-namespaces, -modernize-unary-static-assert, + -performance-avoid-endl, -performance-no-automatic-move, -performance-unnecessary-copy-initialization, -performance-unnecessary-value-param, diff --git a/.gitmodules b/.gitmodules index 8a38304f74..43f7ab0523 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "fin"] path = fin - url = https://github.com/ROCmSoftwarePlatform/MIFin.git + url = https://github.com/ROCm/MIFin.git branch = develop diff --git a/CHANGELOG.md b/CHANGELOG.md index dad2b7bc87..f85f66a0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,28 +160,28 @@ INI8x4 support and fix minor issues and bugs. - typo in components dependency variable in CMakeLists.txt - issues with COMgr backed online compilation for HIP kernels ### Known issues -- This release may show warnings for "obsolete configs" in the performance database. This can be fixed by rerunning tuning on a specfic network; [see tuning documentation](https://rocmsoftwareplatform.github.io/MIOpen/doc/html/perfdatabase.html#miopen-find-enforce) +- This release may show warnings for "obsolete configs" in the performance database. This can be fixed by rerunning tuning on a specfic network; [see tuning documentation](https://ROCm.github.io/MIOpen/doc/html/perfdatabase.html#miopen-find-enforce) ## MIOpen 2.7.0 for ROCm 3.8.0 -- This release contains a new reduction API; see [API documentation](https://rocmsoftwareplatform.github.io/MIOpen/doc/html/apireference.html) for more information. Additional features for embedded builds have been added, and further support for 3D convolutional networks. +- This release contains a new reduction API; see [API documentation](https://ROCm.github.io/MIOpen/doc/html/apireference.html) for more information. Additional features for embedded builds have been added, and further support for 3D convolutional networks. ### Added - additional tunings into performance database - general reduction API - cmake flag for embedding binary database into a static MIOpen build - cmake flag for embedding system find-db text files into static MIOpen build ### Fixed -- issue with GEMM workspace size calculation for backwards data convolutions [#381](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/381) -- issue with 3D pooling indexing [#365](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/365) +- issue with GEMM workspace size calculation for backwards data convolutions [#381](https://github.com/ROCm/MIOpen/issues/381) +- issue with 3D pooling indexing [#365](https://github.com/ROCm/MIOpen/issues/365) ## MIOpen 2.6.0 for ROCm 3.7.0 ### Notes - This release contains convolution performance improvements, improved multi-threading behavior, and improved stability for half precision convolutions. Initial iteration time has been reduced with the introduction of hybrid find mode. Builds for a static library have been refined for this release. ### Added -- MIOPEN_FIND_MODE=3 as the new default convolution Find mode; see documentation [here](https://rocmsoftwareplatform.github.io/MIOpen/doc/html/find_and_immediate.html#find-modes) for details +- MIOPEN_FIND_MODE=3 as the new default convolution Find mode; see documentation [here](https://ROCm.github.io/MIOpen/doc/html/find_and_immediate.html#find-modes) for details - a more runtime-parameterized version of pooling to reduce the number of online compilations - Improved the performance of backwards spatial batch normalization for small images ### Fixed -- issue with std::logic_error in SQLite deleter [#306](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/306) +- issue with std::logic_error in SQLite deleter [#306](https://github.com/ROCm/MIOpen/issues/306) - issues with half precision stability for convolutions - issues with multi-threaded SQLite database accesses - issues with 3-D convolutions and incorrect parameters @@ -213,7 +213,7 @@ INI8x4 support and fix minor issues and bugs. - Improved performance for implicitGEMM and Winograd algorithms - Improved database locking ### Fixed -- issue with GPU memory segmentation fault on asymmetric padding [#142](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/142) +- issue with GPU memory segmentation fault on asymmetric padding [#142](https://github.com/ROCm/MIOpen/issues/142) ## MIOpen 2.3.0 for ROCm 3.1.0 ### Notes @@ -225,7 +225,7 @@ INI8x4 support and fix minor issues and bugs. - full CO v3 support for all kernels in MIOpen - new Winograd group convolution kernels - an API to query MIOpen's version -- parallel compilation in initial convolutional algorithm search; partial solution to [#130](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/130) +- parallel compilation in initial convolutional algorithm search; partial solution to [#130](https://github.com/ROCm/MIOpen/issues/130) - SQLite binary program cache - Improved logging across all layers - Improved MIOpen's internal design for calling convolutional solvers @@ -239,7 +239,7 @@ INI8x4 support and fix minor issues and bugs. - support for multiple ROCm installations - additional support for code object v3 ### Fixed -- issue with incorrect LRN calculation [#127](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/127) +- issue with incorrect LRN calculation [#127](https://github.com/ROCm/MIOpen/issues/127) - incorrect performance database documentation - issue with incorrect workspace calculation in group convolutions - issue with unsupported hardware instructions used with inline assembly @@ -248,7 +248,7 @@ INI8x4 support and fix minor issues and bugs. ### Notes - This release contains bug fixes, performance improvements, and expanded applicability for specific convolutional algorithms. - MIOpen has posted a citable paper on ArXiv [here](https://arxiv.org/abs/1910.00078). -- An SQLite database has been added to replace the text-based performance database. While the text file still exists, by default SQLite is used over the text-based performance database; see [documentation](https://rocmsoftwareplatform.github.io/MIOpen/doc/html/perfdatabase.html) from more details. +- An SQLite database has been added to replace the text-based performance database. While the text file still exists, by default SQLite is used over the text-based performance database; see [documentation](https://ROCm.github.io/MIOpen/doc/html/perfdatabase.html) from more details. ### Added - per solution algorithm filtering environmental variable for debugging - SQLite3 database and build dependency. The text-based performance database support is deprecated and will be removed in the next release. @@ -279,7 +279,7 @@ INI8x4 support and fix minor issues and bugs. - Improved performance of convolutions layers ### Fixed - issue with NaN appearing on batch normalization backwards pass in fp16 -- softmax kernel bug in log mode [#112](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/112) +- softmax kernel bug in log mode [#112](https://github.com/ROCm/MIOpen/issues/112) - ROCm gfx803 support issue [#869](https://github.com/RadeonOpenCompute/ROCm/issues/869) ### Removed - MIOpenGEMM as a requirement for the HIP backend. It is now optional. @@ -323,17 +323,17 @@ INI8x4 support and fix minor issues and bugs. - a shipped System Find-Db containing offline run Find() results - an additional, faster batch norm assembly kernel for fp16 - CTC loss layer -- MIOpenDriver as a default component in MIOpen's build [#34](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/34) +- MIOpenDriver as a default component in MIOpen's build [#34](https://github.com/ROCm/MIOpen/issues/34) - Improved performance of 1x1 stride 2 fp32 convolutions in the forward and backwards data passes - Improved 3-D convolution stability - Improved applicability of direct convolution backwards weights for 2x2, 5x10, and 5x20 filter sizes - Improved maintainability in kernels and cpp code ### Changed -- Updated rocBLAS minimum version to branch [master-rocm-2.6](https://github.com/ROCmSoftwarePlatform/rocBLAS/tree/master-rocm-2.6) +- Updated rocBLAS minimum version to branch [master-rocm-2.6](https://github.com/ROCm/rocBLAS/tree/master-rocm-2.6) ### Fixed -- C compatability for boolean types in C API [#103](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/103) -- incorrect calculation in per-activation batch norm backwards pass [#104](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/104) -- bug [#95](https://github.com/ROCmSoftwarePlatform/MIOpen/issues/95) with asm batch norm ISA +- C compatability for boolean types in C API [#103](https://github.com/ROCm/MIOpen/issues/103) +- incorrect calculation in per-activation batch norm backwards pass [#104](https://github.com/ROCm/MIOpen/issues/104) +- bug [#95](https://github.com/ROCm/MIOpen/issues/95) with asm batch norm ISA - IsApplicable bug in Conv3x3Asm for group convolutions ## MIOpen 1.8.1 for ROCm 2.5.0 @@ -476,7 +476,7 @@ INI8x4 support and fix minor issues and bugs. ### Known issues - RNNs do not support fp16 - Training with CNNs does not support fp16 -- Users may encounter a warning that their performance database is out of date. The performance database can be updated by setting the environment variable for just the initial run of an application: `MIOPEN_FIND_ENFORCE=search`. For more information on the performance database, see: https://rocmsoftwareplatform.github.io/MIOpen/doc/html/perfdatabase.html# +- Users may encounter a warning that their performance database is out of date. The performance database can be updated by setting the environment variable for just the initial run of an application: `MIOPEN_FIND_ENFORCE=search`. For more information on the performance database, see: https://ROCm.github.io/MIOpen/doc/html/perfdatabase.html# ## MIOpen 1.4.1 for ROCm 1.0.0 ### Added: @@ -485,7 +485,7 @@ INI8x4 support and fix minor issues and bugs. ### Known issues - RNNs do not support fp16 - Training with CNNs does not support fp16 -- Users may encounter a warning that their performance database is out of date. The performance database can be updated by setting the environment variable for just the initial run of an application: `MIOPEN_FIND_ENFORCE=search`. For more information on the performance database, see: https://rocmsoftwareplatform.github.io/MIOpen/doc/html/perfdatabase.html# +- Users may encounter a warning that their performance database is out of date. The performance database can be updated by setting the environment variable for just the initial run of an application: `MIOPEN_FIND_ENFORCE=search`. For more information on the performance database, see: https://ROCm.github.io/MIOpen/doc/html/perfdatabase.html# ## MIOpen 1.4.0 for ROCm 1.0.0 ### Notes: diff --git a/CMakeLists.txt b/CMakeLists.txt index f117352742..5ad9f74f29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,7 @@ if(NOT WIN32 AND NOT APPLE) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s") endif() -rocm_setup_version(VERSION 3.1.0) +rocm_setup_version(VERSION 3.2.0) list( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ) include(TargetFlags) @@ -163,10 +163,8 @@ option(MIOPEN_DISABLE_USERDB "Disable user database access" ${MIOPEN_EMBED_BUILD if(MIOPEN_EMBED_BUILD) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build as a shared library" FORCE) option(MIOPEN_USE_HIP_KERNELS "Use HIP kernels." Off) - option(MIOPEN_BUILD_DRIVER "Build MIOpenDriver" Off) else() option(MIOPEN_USE_HIP_KERNELS "Use HIP kernels." On) - option(MIOPEN_BUILD_DRIVER "Build MIOpenDriver" On) endif() if(MIOPEN_EMBED_BUILD) @@ -184,6 +182,10 @@ set( MIOPEN_BACKEND ${MIOPEN_DEFAULT_BACKEND} CACHE STRING set_property( CACHE MIOPEN_BACKEND PROPERTY STRINGS OpenCL HIP HIPOC HIPNOGPU) +set_var_to_condition(MIOPEN_BUILD_DRIVER_DEFAULT (NOT MIOPEN_EMBED_BUILD) AND (NOT (MIOPEN_BACKEND STREQUAL "HIPNOGPU"))) +option(MIOPEN_BUILD_DRIVER "Build MIOpenDriver (and use it in tests)" ${MIOPEN_BUILD_DRIVER_DEFAULT}) +message(STATUS "MIOPEN_BUILD_DRIVER: ${MIOPEN_BUILD_DRIVER}" ) + # OpenCL 1.2 if( MIOPEN_BACKEND STREQUAL "OpenCL") set(MIOPEN_BACKEND_OPENCL 1) @@ -215,15 +217,6 @@ endif() # HIP is always required find_package(hip REQUIRED PATHS /opt/rocm) message(STATUS "Build with HIP ${hip_VERSION}") -target_flags(HIP_COMPILER_FLAGS hip::device) -# Remove cuda arch flags -string(REGEX REPLACE --cuda-gpu-arch=[a-z0-9]+ "" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") -string(REGEX REPLACE --offload-arch=[a-z0-9:+-]+ "" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") -# Skip library paths since hip will incorrectly treat it as a source file -string(APPEND HIP_COMPILER_FLAGS " ") -foreach(_unused RANGE 2) - string(REGEX REPLACE " /[^ ]+\\.(a|so) " " " HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") -endforeach() # Override HIP version in config.h, if necessary. # The variables set by find_package() can't be overwritten, @@ -261,6 +254,22 @@ set(MIOPEN_hip_VERSION ${MIOPEN_hip_VERSION_MAJOR}.${MIOPEN_hip_VERSION_MINOR}.$ set_var_to_condition(MIOPEN_USE_HIPRTC_DEFAULT (MIOPEN_USE_COMGR AND (MIOPEN_hip_VERSION VERSION_GREATER_EQUAL 5))) option(MIOPEN_USE_HIPRTC "Use HIPRTC to build HIP kernels instead of COMGR" ${MIOPEN_USE_HIPRTC_DEFAULT}) +# Do not append system include directories to HIP compiler flags when HIPRTC is used +set_var_to_condition(MIOPEN_HIP_COMPILER_USE_SYSTEM_INCLUDE_DIRECTORIES_DEFAULT + (NOT (MIOPEN_USE_HIPRTC AND (MIOPEN_hip_VERSION VERSION_GREATER_EQUAL 6.1.40091)))) +option(MIOPEN_HIP_COMPILER_USE_SYSTEM_INCLUDE_DIRECTORIES "Append include directories to compiler flags" + ${MIOPEN_HIP_COMPILER_USE_SYSTEM_INCLUDE_DIRECTORIES_DEFAULT}) + +target_flags(HIP_COMPILER_FLAGS hip::device) +# Remove cuda arch flags +string(REGEX REPLACE --cuda-gpu-arch=[a-z0-9]+ "" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") +string(REGEX REPLACE --offload-arch=[a-z0-9:+-]+ "" HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") +# Skip library paths since hip will incorrectly treat it as a source file +string(APPEND HIP_COMPILER_FLAGS " ") +foreach(_unused RANGE 2) + string(REGEX REPLACE " /[^ ]+\\.(a|so) " " " HIP_COMPILER_FLAGS "${HIP_COMPILER_FLAGS}") +endforeach() + # WORKAROUND_SWDEV_413293 # Assume that any HIP kernel can be launched with non-uniform block size; otherwise # the "Failed to launch kernel: invalid argument" error may happen at run time. @@ -300,7 +309,7 @@ if( MIOPEN_BACKEND STREQUAL "HIP" OR MIOPEN_BACKEND STREQUAL "HIPOC" OR MIOPEN_B endif() set(MIOPEN_BACKEND_HIP 1) - find_program(HIP_OC_COMPILER clang + find_program(HIP_OC_COMPILER amdclang PATH_SUFFIXES bin PATHS /opt/rocm @@ -358,27 +367,37 @@ if(MIOPEN_USE_MLIR) if(NOT ${BUILD_SHARED_LIBS} AND ${MIOPEN_USE_COMGR}) message(FATAL_ERROR "Potential symbol conflict between mlir and comgr in static build") endif() - # Try to find package rocMLIR - # REQUIRED is omitted since we do not want cmake to abort if the package is not found - find_package(rocMLIR 1.0.0 CONFIG) - if(NOT rocMLIR_FOUND) - message(STATUS "Falling back to find library libMLIRMIOpen") - # Backward compatibility with ROCm 5.3 - # If the rocMLIR package is not found, try to find the library libMLIRMIOpen directly - find_library(LIBMLIRMIOPEN MLIRMIOpen REQUIRED) - if(NOT LIBMLIRMIOPEN) - message(FATAL_ERROR "library libMLIRMIOpen not found, please reinstall dependencies. \ - Refer to https://github.com/ROCm/MIOpen#installing-the-dependencies") - else() - message(STATUS "Build with library libMLIRMIOpen: " ${LIBMLIRMIOPEN}) - set(rocMLIR_VERSION 0.0.1) - endif() + if(WIN32) + # Windows does not support earlier ROCm versions hence no fallback to MLIRMIOpen. + find_package(rocMLIR 1.0.0 CONFIG REQUIRED) else() - message(STATUS "Build with rocMLIR::rockCompiler ${rocMLIR_VERSION}") + # Try to find package rocMLIR + # REQUIRED is omitted since we do not want cmake to abort if the package is not found + find_package(rocMLIR 1.0.0 CONFIG) + if(NOT rocMLIR_FOUND) + message(STATUS "Falling back to find library libMLIRMIOpen") + # Backward compatibility with ROCm 5.3 + # If the rocMLIR package is not found, try to find the library libMLIRMIOpen directly + find_library(LIBMLIRMIOPEN MLIRMIOpen REQUIRED) + if(NOT LIBMLIRMIOPEN) + message(FATAL_ERROR "library libMLIRMIOpen not found, please reinstall dependencies. \ + Refer to https://github.com/ROCm/MIOpen#installing-the-dependencies") + else() + message(STATUS "Build with library libMLIRMIOpen: " ${LIBMLIRMIOPEN}) + set(rocMLIR_VERSION 0.0.1) + endif() + endif() endif() + message(STATUS "Build with rocMLIR::rockCompiler ${rocMLIR_VERSION}") endif() -set(MIOPEN_PACKAGE_REQS "hip-rocclr") +# Update HIP Runtime Package Dependency +if(ENABLE_ASAN_PACKAGING) + set(DEPENDS_HIP_RUNTIME "hip-runtime-amd-asan" ) +else() + set(DEPENDS_HIP_RUNTIME "hip-runtime-amd" ) +endif() +set(MIOPEN_PACKAGE_REQS "${DEPENDS_HIP_RUNTIME}") # Online assembler find_program(MIOPEN_AMDGCN_ASSEMBLER @@ -412,6 +431,10 @@ endif() option(Boost_USE_STATIC_LIBS "Use boost static libraries" ON) set(BOOST_COMPONENTS filesystem) +if(MIOPEN_BUILD_DRIVER) + # boost core is a header only component that can't be found by find_package + # list(APPEND BOOST_COMPONENTS core) +endif() add_definitions(-DBOOST_ALL_NO_LIB=1) find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) @@ -430,7 +453,7 @@ else() set( MIOPEN_INSTALL_DIR miopen) set( DATA_INSTALL_DIR ${CMAKE_INSTALL_DATADIR}/${MIOPEN_INSTALL_DIR} ) set( DATABASE_INSTALL_DIR ${DATA_INSTALL_DIR}/db ) -endif() +endif() if(MIOPEN_ENABLE_AI_KERNEL_TUNING OR MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK) find_package(frugally-deep CONFIG REQUIRED) @@ -489,6 +512,13 @@ if(MIOPEN_OFFLINE_COMPILER_PATHS_V2) set(MIOPEN_PACKAGE_REQS "${MIOPEN_PACKAGE_REQS}, rocm-core") endif() +if(MIOPEN_BUILD_DRIVER) + # PR #2785 MIOpenDriver to use rocrand to init buffers + find_package(rocrand REQUIRED) + message(STATUS "rocrand_VERSION=${rocrand_VERSION}") + set(MIOPEN_PACKAGE_REQS "${MIOPEN_PACKAGE_REQS}, rocrand") +endif() + if(MIOPEN_BACKEND STREQUAL "HIP") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${MIOPEN_PACKAGE_REQS}") set(CPACK_RPM_PACKAGE_REQUIRES "${MIOPEN_PACKAGE_REQS}") @@ -513,13 +543,34 @@ file(MAKE_DIRECTORY ${KERNELS_BINARY_DIR}) add_custom_target(generate_kernels ALL) +set(MIOPEN_USE_SQLITE_PERFDB Off CACHE BOOL "Use sqlite perfdb instead of text-based.") +if(MIOPEN_USE_SQLITE_PERFDB) + set(PERFDB_SUFFIX "") +else() + set(PERFDB_SUFFIX ".txt") +endif() + function(unpack_db db_bzip2_file) get_filename_component(__fname ${db_bzip2_file} NAME_WLE) add_custom_command(OUTPUT ${KERNELS_BINARY_DIR}/${__fname} COMMAND ${UNZIPPER} -k ${db_bzip2_file} > ${KERNELS_BINARY_DIR}/${__fname}) string(REPLACE "." "_" __tname ${__fname}) add_custom_target(generate_${__tname} ALL DEPENDS ${KERNELS_BINARY_DIR}/${__fname}) - add_dependencies(generate_kernels generate_${__tname}) + + get_filename_component(__extension ${__fname} LAST_EXT) + + if(NOT MIOPEN_USE_SQLITE_PERFDB AND __extension STREQUAL ".db") + add_custom_command(OUTPUT ${KERNELS_BINARY_DIR}/${__fname}.txt + DEPENDS sqlite2txt generate_${__tname} + COMMAND $ ${KERNELS_BINARY_DIR}/${__fname} ${KERNELS_BINARY_DIR}/${__fname}.txt + ) + add_custom_target(generate_${__tname}_txt ALL DEPENDS ${KERNELS_BINARY_DIR}/${__fname}.txt) + add_dependencies(generate_kernels generate_${__tname}_txt) + set(__fname ${__fname}.txt) + else() + add_dependencies(generate_kernels generate_${__tname}) + endif() + set(__fname ${__fname} PARENT_SCOPE) endfunction() file(GLOB PERF_DB_BZIP_FILES CONFIGURE_DEPENDS "${KERNELS_SOURCE_DIR}/*.db.bz2") @@ -527,7 +578,6 @@ file(GLOB FIND_DB_BZIP_FILES CONFIGURE_DEPENDS "${KERNELS_SOURCE_DIR}/*.fdb.txt. foreach(DB_BZIP_FILE ${PERF_DB_BZIP_FILES} ${FIND_DB_BZIP_FILES}) unpack_db(${DB_BZIP_FILE}) - get_filename_component(__fname ${DB_BZIP_FILE} NAME_WLE) if(MIOPEN_EMBED_DB STREQUAL "" AND NOT MIOPEN_DISABLE_SYSDB AND NOT ENABLE_ASAN_PACKAGING) install(FILES ${KERNELS_BINARY_DIR}/${__fname} DESTINATION ${DATABASE_INSTALL_DIR}) @@ -584,6 +634,10 @@ endif() # End KDB package creation +if(NOT MIOPEN_TEST_DISCRETE) +list(APPEND CPACK_COMPONENTS_ALL client) +endif() + rocm_create_package( NAME MIOpen-${MIOPEN_BACKEND} DESCRIPTION "AMD's DNN Library" @@ -691,10 +745,11 @@ enable_cppcheck( constVariablePointer useStlAlgorithm uselessOverride - unusedScopedObject + unusedScopedObject FORCE SOURCES addkernels/ + tools/sqlite2txt/ # driver/ include/ src/ @@ -713,6 +768,9 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +if(NOT MIOPEN_USE_SQLITE_PERFDB) + add_subdirectory(tools/sqlite2txt) +endif() add_subdirectory(addkernels) add_subdirectory(src) if(MIOPEN_BUILD_DRIVER) diff --git a/Dockerfile b/Dockerfile index 994ede812b..d4a5f3f7ff 100755 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN dpkg --add-architecture i386 # Install preliminary dependencies RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ + "linux-headers-$(uname -r)" "linux-modules-extra-$(uname -r)" \ apt-utils \ ca-certificates \ curl \ @@ -18,13 +19,13 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn RUN curl -fsSL https://repo.radeon.com/rocm/rocm.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/rocm-keyring.gpg -RUN wget https://repo.radeon.com/amdgpu-install/6.0.2/ubuntu/jammy/amdgpu-install_6.0.60002-1_all.deb --no-check-certificate +RUN wget https://repo.radeon.com/amdgpu-install/6.1/ubuntu/jammy/amdgpu-install_6.1.60100-1_all.deb --no-check-certificate RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ - ./amdgpu-install_6.0.60002-1_all.deb + ./amdgpu-install_6.1.60100-1_all.deb # Add rocm repository -RUN export ROCM_APT_VER=6.0.2;\ +RUN export ROCM_APT_VER=6.1;\ echo $ROCM_APT_VER &&\ sh -c 'echo deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/rocm-keyring.gpg] https://repo.radeon.com/amdgpu/$ROCM_APT_VER/ubuntu jammy main > /etc/apt/sources.list.d/amdgpu.list' &&\ sh -c 'echo deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/rocm-keyring.gpg] https://repo.radeon.com/rocm/apt/$ROCM_APT_VER jammy main > /etc/apt/sources.list.d/rocm.list' @@ -49,8 +50,8 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \ python3-dev \ python3-pip \ python3-venv \ - rocblas \ rocm-developer-tools \ + rocm-llvm-dev \ rpm \ software-properties-common && \ apt-get clean && \ diff --git a/Jenkinsfile b/Jenkinsfile index b34af21400..3a5142a360 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -56,7 +56,9 @@ def cmake_build(Map conf=[:]){ { make_targets = 'install ' + make_targets setup_args = " -DBUILD_DEV=Off -DCMAKE_INSTALL_PREFIX=${miopen_install_path}" + setup_args - } else{ + } else if(package_build == true) { + setup_args = ' -DBUILD_DEV=Off' + setup_args + } else { setup_args = ' -DBUILD_DEV=On' + setup_args } @@ -253,7 +255,7 @@ def buildHipClangJob(Map conf=[:]){ def lfs_pull = conf.get("lfs_pull", false) def retimage - gitStatusWrapper(credentialsId: "${env.status_wrapper_creds}", gitHubContext: "Jenkins - ${variant}", account: 'ROCmSoftwarePlatform', repo: 'MIOpen') { + gitStatusWrapper(credentialsId: "${env.status_wrapper_creds}", gitHubContext: "Jenkins - ${variant}", account: 'ROCm', repo: 'MIOpen') { try { (retimage, image) = getDockerImage(conf) if (needs_gpu) { @@ -467,7 +469,7 @@ pipeline { description: "") booleanParam( name: "TARGET_GFX908", - defaultValue: env.BRANCH_NAME == env.NIGHTLY_BRANCH ? true : false, + defaultValue: env.BRANCH_NAME == "develop" ? true : false, description: "") booleanParam( name: "TARGET_GFX90A", @@ -481,6 +483,10 @@ pipeline { name: "TARGET_NAVI21", defaultValue: false, description: "") + booleanParam( + name: "TARGET_NAVI32", + defaultValue: false, + description: "") booleanParam( name: "DATATYPE_NA", defaultValue: true, @@ -624,28 +630,28 @@ pipeline { expression { params.BUILD_SMOKE_FP32 && params.DATATYPE_FP32 } } parallel{ - stage('Fp32 Hip AnyGPU') { + stage('Fp32 Hip gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } steps{ buildHipClangJobAndReboot(make_targets: Smoke_targets) } } - stage('Fp32 Hip Debug AnyGPU') { + stage('Fp32 Hip Debug gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } steps{ buildHipClangJobAndReboot(build_type: 'debug', make_targets: Smoke_targets) } @@ -663,19 +669,6 @@ pipeline { buildHipClangJobAndReboot(build_type: 'debug', make_targets: Smoke_targets) } } - stage('Fp32 Hip Debug gfx90a') { - when { - beforeAgent true - expression { params.TARGET_GFX90A } - } - options { - retry(2) - } - agent{ label rocmnode("gfx90a") } - steps{ - buildHipClangJobAndReboot(build_type: 'debug', make_targets: Smoke_targets) - } - } stage('Fp32 Hip Debug gfx94X') { when { beforeAgent true @@ -696,15 +689,15 @@ pipeline { expression { params.BUILD_SMOKE_AUX1 && params.DATATYPE_FP32 } } parallel{ - stage('Fp32 Hip Debug NOCOMGR AnyGPU') { + stage('Fp32 Hip Debug NOCOMGR gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } environment{ // Can be removed altogether with when WORKAROUND_SWDEV_290754. NOCOMGR_build_cmd = "CTEST_PARALLEL_LEVEL=4 MIOPEN_CONV_PRECISE_ROCBLAS_TIMING=0 MIOPEN_LOG_LEVEL=5 make -j\$(nproc) check" @@ -713,15 +706,15 @@ pipeline { buildHipClangJobAndReboot( build_type: 'debug', setup_flags: NOCOMGR_flags, build_cmd: NOCOMGR_build_cmd, test_flags: ' --verbose ') } } - stage('Fp32 Hip Debug NOMLIR AnyGPU') { + stage('Fp32 Hip Debug NOMLIR gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } environment{ // Can be removed altogether with when WORKAROUND_SWDEV_290754. NOMLIR_build_cmd = "CTEST_PARALLEL_LEVEL=4 MIOPEN_CONV_PRECISE_ROCBLAS_TIMING=0 MIOPEN_LOG_LEVEL=5 make -j\$(nproc) check" @@ -730,15 +723,15 @@ pipeline { buildHipClangJobAndReboot( build_type: 'debug', setup_flags: NOMLIR_flags, build_cmd: NOMLIR_build_cmd, test_flags: ' --verbose ') } } - stage('Fp32 Hip Debug NOCK AnyGPU Build-Only') { + stage('Fp32 Hip Debug NOCK gfx90a Build-Only') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } steps{ buildHipClangJobAndReboot( build_type: 'debug', setup_flags: "-DMIOPEN_USE_COMPOSABLEKERNEL=Off", make_targets: "") } @@ -759,28 +752,28 @@ pipeline { buildHipClangJobAndReboot( build_type: 'debug', setup_flags: Embedded_flags, build_env: extra_log_env, test_flags: ' --verbose ') } } - stage('Fp32 Hip Static AnyGPU') { + stage('Fp32 Hip Static gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } steps{ buildHipClangJobAndReboot( setup_flags: "-DBUILD_SHARED_LIBS=Off", mlir_build: 'OFF') } } - stage('Fp32 Hip Normal-Find AnyGPU') { + stage('Fp32 Hip Normal-Find gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } environment{ make_targets = "test_conv2d" execute_cmd = "MIOPEN_CONV_PRECISE_ROCBLAS_TIMING=0 bin/test_conv2d --disable-verification-cache" @@ -789,15 +782,15 @@ pipeline { buildHipClangJobAndReboot(make_targets: make_targets, execute_cmd: execute_cmd, find_mode: "Normal") } } - stage('Fp32 Hip Fast-Find AnyGPU') { + stage('Fp32 Hip Fast-Find gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } environment{ make_targets = "test_conv2d" execute_cmd = "MIOPEN_FIND_MODE=2 CTEST_PARALLEL_LEVEL=4 MIOPEN_CONV_PRECISE_ROCBLAS_TIMING=0 bin/test_conv2d --disable-verification-cache" @@ -806,19 +799,32 @@ pipeline { buildHipClangJobAndReboot( make_targets: make_targets, execute_cmd: execute_cmd) } } - stage('Fp32 Hip AnyGPU') { + stage('Fp32 Hip gfx90a') { when { beforeAgent true - expression { params.TARGET_VEGA20 || params.TARGET_VEGA10 || params.TARGET_GFX908 || params.TARGET_GFX90A } + expression { params.TARGET_GFX90A } } options { retry(2) } - agent{ label rocmnode("vega || gfx908 || gfx90a") } + agent{ label rocmnode("gfx90a") } steps{ buildHipClangJobAndReboot() } } + stage('Fp32 Hip SqlitePerfdb gfx90a') { + when { + beforeAgent true + expression { params.TARGET_GFX90A } + } + options { + retry(2) + } + agent{ label rocmnode("gfx90a") } + steps{ + buildHipClangJobAndReboot(make_targets: Smoke_targets, setup_flags: "-DMIOPEN_USE_SQLITE_PERF_DB=On") + } + } } } stage("Smoke Fp16/Bf16/Int8") { @@ -1045,6 +1051,19 @@ pipeline { buildHipClangJobAndReboot(setup_flags: Full_test + Fp16_flags, build_cmd: Navi21_build_cmd) } } + stage('Fp16 Hip All gfx1101') { + when { + beforeAgent true + expression { params.TARGET_NAVI32 && params.DATATYPE_FP16 } + } + options { + retry(2) + } + agent{ label rocmnode("navi32") } + steps{ + buildHipClangJobAndReboot(setup_flags: Full_test + Fp16_flags) + } + } stage('Fp32 Hip All gfx908') { when { beforeAgent true @@ -1133,6 +1152,19 @@ pipeline { buildHipClangJobAndReboot(setup_flags: Full_test, build_cmd: Navi21_build_cmd, build_install: "true") } } + stage('Fp32 Hip All Install gfx1101') { + when { + beforeAgent true + expression { params.TARGET_NAVI32 && params.DATATYPE_FP32 } + } + options { + retry(2) + } + agent{ label rocmnode("navi32") } + steps{ + buildHipClangJobAndReboot(setup_flags: Full_test, build_install: "true") + } + } stage('Fp16 Hip All Install gfx908') { when { beforeAgent true diff --git a/README.md b/README.md index 7cd5c4735f..5e0f79b425 100755 --- a/README.md +++ b/README.md @@ -1,23 +1,22 @@ # MIOpen -AMD's library for high performance machine learning primitives. -Sources and binaries can be found at [MIOpen's GitHub site](https://github.com/ROCm/MIOpen). -The latest released documentation can be read online [here](https://rocm.docs.amd.com/projects/MIOpen/en/latest/index.html). +MIOpen is AMD's library for high-performance machine learning primitives. -MIOpen supports two programming models, or backends: +You can find sources and binaries in our [GitHub repository](https://github.com/ROCm/MIOpen) and +our documentation on +[ROCm docs](https://rocm.docs.amd.com/projects/MIOpen/en/latest/index.html). -1. [HIP](https://github.com/ROCm/HIP) -2. OpenCL (deprecated). +MIOpen supports these programming models (backends): -## Documentation +* [HIP](https://rocm.docs.amd.com/projects/HIP/en/latest/) +* OpenCL (deprecated) -For a detailed description of the **MIOpen** library see the [Documentation](https://rocmdocs.amd.com/projects/MIOpen/en/latest/). +## Building our documentation -### How to build documentation +To build the MIOpen documentation locally, run the following code from within the `docs` folder in +our repository: -Run the steps below to build documentation locally. - -```shell +``` shell cd docs pip3 install -r sphinx/requirements.txt @@ -25,202 +24,248 @@ pip3 install -r sphinx/requirements.txt python3 -m sphinx -T -E -b html -d _build/doctrees -D language=en . _build/html ``` -## Prerequisites - -* More information about ROCm stack via [ROCm Information Portal](https://docs.amd.com/). -* A ROCm enabled platform, more info [here](https://rocmdocs.amd.com/en/latest/). -* Base software stack, which includes: - * HIP - - * HIP and HCC libraries and header files. - * OpenCL - OpenCL libraries and header files. -* [ROCm cmake](https://github.com/ROCm/rocm-cmake) - provide cmake modules for common build tasks needed for the ROCM software stack. -* [Half](http://half.sourceforge.net/) - IEEE 754-based half-precision floating point library -* [Boost](http://www.boost.org/) - * MIOpen uses `boost-system` and `boost-filesystem` packages to enable persistent [kernel cache](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html) - * Version 1.79 is recommended, older version may need patches to work on newer systems, e.g. boost1{69,70,72} w/glibc-2.34 -* [SQLite3](https://sqlite.org/index.html) - reading and writing performance database -* lbzip2 - multi-threaded compress or decompress utility -* [rocBLAS](https://github.com/ROCm/rocBLAS) - AMD library for Basic Linear Algebra Subprograms (BLAS) on the ROCm platform. +## Installing MIOpen + +To install MIOpen, you must first install these prerequisites: + +* A [ROCm](https://rocm.docs.amd.com/)-enabled platform +* A base software stack that includes either: + *HIP (HIP and HCC libraries and header files) + * OpenCL (OpenCL libraries and header files)--this is now deprecated +* [ROCm CMake](https://github.com/ROCm/rocm-cmake): provides CMake modules for common build + tasks needed for the ROCm software stack +* [Half](http://half.sourceforge.net/): IEEE 754-based, half-precision floating-point library +* [Boost](http://www.boost.org/): Version 1.79 is recommended, as older versions may need patches to + work on newer systems + * MIOpen uses `boost-system` and `boost-filesystem` packages to enable persistent + [kernel cache](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html) +* [SQLite3](https://sqlite.org/index.html): A reading and writing performance database +* lbzip2: A multi-threaded compress or decompress utility +* [rocBLAS](https://github.com/ROCm/rocBLAS): AMD's library for Basic Linear Algebra Subprograms + (BLAS) on the ROCm platform. * Minimum version branch for pre-ROCm 3.5 [master-rocm-2.10](https://github.com/ROCm/rocBLAS/tree/master-rocm-2.10) * Minimum version branch for post-ROCm 3.5 [master-rocm-3.5](https://github.com/ROCm/rocBLAS/tree/master-rocm-3.5) -* [MLIR](https://github.com/ROCm/rocMLIR) - (Multi-Level Intermediate Representation) with its MIOpen dialect to support and complement kernel development. -* [Composable Kernel](https://github.com/ROCm/composable_kernel) - C++ templated device library for GEMM-like and reduction-like operators. - -## Installing MIOpen with pre-built packages +* [Multi-Level Intermediate Representation (MLIR)](https://github.com/ROCm/rocMLIR) with its + MIOpen dialect to support and complement kernel development +* [Composable Kernel](https://github.com/ROCm/composable_kernel): A C++ templated device library + for GEMM-like and reduction-like operators. -MIOpen can be installed on Ubuntu using `apt-get`. +### Installing with pre-built packages -For OpenCL backend: `apt-get install miopen-opencl` +You can install MIOpen on Ubuntu using `apt-get install miopen-hip`. -For HIP backend: `apt-get install miopen-hip` +If using OpenCL, you can use `apt-get install miopen-opencl` (but this is not recommended, as OpenCL +is deprecated). -Currently both the backends cannot be installed on the same system simultaneously. If a different backend other than what currently exists on the system is desired, please uninstall the existing backend completely and then install the new backend. +Note that you can't install both backends on the same system simultaneously. If you want a different +backend other than what currently exists, completely uninstall the existing backend prior to installing +the new backend. -## Installing MIOpen kernels package +### Installing with a kernels package -MIOpen provides an optional pre-compiled kernels package to reduce the startup latency. These precompiled kernels comprise a select set of popular input configurations and will expand in future release to contain additional coverage. +MIOpen provides an optional pre-compiled kernels package to reduce startup latency. These +precompiled kernels comprise a select set of popular input configurations. We'll expand these kernels +in future releases to include additional coverage. -Note that all compiled kernels are locally cached in the folder `$HOME/.cache/miopen/`, so precompiled kernels reduce the startup latency only for the first execution of a neural network. Precompiled kernels do not reduce startup time on subsequent runs. +Note that all compiled kernels are locally cached in the `$HOME/.cache/miopen/` folder, so +precompiled kernels reduce the startup latency only for the first run of a neural network. Precompiled +kernels don't reduce startup time on subsequent runs. To install the kernels package for your GPU architecture, use the following command: -```shell +``` shell apt-get install miopenkernels-- ``` -Where `` is the GPU architecture ( for example, `gfx900`, `gfx906`, `gfx1030` ) and `` is the number of CUs available in the GPU (for example 56 or 64 etc). +Where ```` is the GPU architecture (e.g., `gfx900`, `gfx906`, `gfx1030` ) and `` is the +number of CUs available in the GPU (e.g., `56`, `64`). -Not installing these packages would not impact the functioning of MIOpen, since MIOpen will compile these kernels on the target machine once the kernel is run. However, the compilation step may significantly increase the startup time for different operations. +>[!NOTE] +>Not installing these packages doesn't impact the functioning of MIOpen, since MIOpen compiles +>them on the target machine once you run the kernel. However, the compilation step may significantly +>increase the startup time for different operations. -The script `utils/install_precompiled_kernels.sh` provided as part of MIOpen automates the above process, it queries the user machine for the GPU architecture and then installs the appropriate package. It may be invoked as: +The `utils/install_precompiled_kernels.sh` script provided as part of MIOpen automates the preceding +process. It queries the user machine for the GPU architecture and then installs the appropriate +package. You can invoke it using: -```shell +``` shell ./utils/install_precompiled_kernels.sh ``` -The above script depends on the *rocminfo* package to query the GPU architecture. +The preceding script depends on the `rocminfo` package to query the GPU architecture. Refer to +[Installing pre-compiled kernels](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html#installing-pre-compiled-kernels) +for more information. -More info can be found [here](https://github.com/ROCm/MIOpen/blob/develop/docs/cache.md#installing-pre-compiled-kernels). +## Installing dependencies -## Installing the dependencies +You can install dependencies using the `install_deps.cmake` script (`cmake -P install_deps.cmake`). -The dependencies can be installed with the `install_deps.cmake`, script: `cmake -P install_deps.cmake` +By default, this installs to `/usr/local`, but you can specify another location using the `--prefix` +argument: -This will install by default to `/usr/local` but it can be installed in another location with `--prefix` argument: - -```shell +``` shell cmake -P install_deps.cmake --prefix ``` -An example cmake step can be: +An example CMake step is: -```shell +``` shell cmake -P install_deps.cmake --minimum --prefix /root/MIOpen/install_dir ``` -This prefix can used to specify the dependency path during the configuration phase using the `CMAKE_PREFIX_PATH`. +You can use this prefix to specify the dependency path during the configuration phase using +`CMAKE_PREFIX_PATH`. -* MIOpen's HIP backend uses [rocBLAS](https://github.com/ROCm/rocBLAS) by default. Users can install rocBLAS minimum release by using `apt-get install rocblas`. To disable using rocBLAS set the configuration flag `-DMIOPEN_USE_ROCBLAS=Off`. rocBLAS is *not* available for the OpenCL backend. +MIOpen's HIP backend uses [rocBLAS](https://github.com/ROCm/rocBLAS) by default. You can install +rocBLAS' minimum release using `apt-get install rocblas`. To disable rocBLAS, set the configuration flag +`-DMIOPEN_USE_ROCBLAS=Off`. rocBLAS is **not** available with OpenCL. ## Building MIOpen from source -### Configuring with cmake +You can build MIOpen form source with a HIP backend or an OpenCL backend. + +### HIP backend -First create a build directory: +First, create a build directory: ```shell mkdir build; cd build; ``` -Next configure cmake. The preferred backend for MIOpen can be set using the `-DMIOPEN_BACKEND` cmake variable. +Next, configure CMake. You can set the backend using the `-DMIOPEN_BACKEND` CMake variable. -### For the HIP backend (ROCm 3.5 and later), run - -Set the C++ compiler to `clang++`. +Set the C++ compiler to `clang++`. For the HIP backend (ROCm 3.5 and later), run: ```shell export CXX= cmake -DMIOPEN_BACKEND=HIP -DCMAKE_PREFIX_PATH=";;" .. ``` -An example cmake step can be: +An example CMake step is: ```shell export CXX=/opt/rocm/llvm/bin/clang++ && \ cmake -DMIOPEN_BACKEND=HIP -DCMAKE_PREFIX_PATH="/opt/rocm/;/opt/rocm/hip;/root/MIOpen/install_dir" .. ``` -Note: When specifying the path for the `CMAKE_PREFIX_PATH` variable, **do not** use the `~` shorthand for the user home directory. +>[!NOTE] +>When specifying the path for the `CMAKE_PREFIX_PATH` variable, **do not** use the tilde (`~`) +>shorthand to represent the home directory. -### For OpenCL, run +### OpenCL backend -```shell +>[!NOTE] +> OpenCL is deprecated. We recommend using a HIP backend and following the instructions listed in +> the preceding section. + +First, run: + +``` shell cmake -DMIOPEN_BACKEND=OpenCL .. ``` -The above assumes that OpenCL is installed in one of the standard locations. If not, then manually set these cmake variables: +The preceding code assumes OpenCL is installed in one of the standard locations. If not, then manually +set these CMake variables: ```shell cmake -DMIOPEN_BACKEND=OpenCL -DMIOPEN_HIP_COMPILER= -DOPENCL_LIBRARIES= -DOPENCL_INCLUDE_DIRS= .. ``` -And an example setting the dependency path for an envirnment in ROCm 3.5 and later: +Here's an example dependency path for an environment in ROCm 3.5 and later: ```shell cmake -DMIOPEN_BACKEND=OpenCL -DMIOPEN_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ -DCMAKE_PREFIX_PATH="/opt/rocm/;/opt/rocm/hip;/root/MIOpen/install_dir" .. ``` -### Setting Up Locations +### Setting up locations -By default the install location is set to '/opt/rocm', this can be set by using `CMAKE_INSTALL_PREFIX`: +By default, the install location is set to `/opt/rocm`. You can change this using +`CMAKE_INSTALL_PREFIX`: ```shell -cmake -DMIOPEN_BACKEND=OpenCL -DCMAKE_INSTALL_PREFIX= .. +cmake -DMIOPEN_BACKEND=HIP -DCMAKE_INSTALL_PREFIX= .. ``` -### System Performance Database and User Database +### System performance database and user database -The default path to the System PerfDb is `miopen/share/miopen/db/` within install location. The default path to the User PerfDb is `~/.config/miopen/`. For development purposes, setting `BUILD_DEV` will change default path to both database files to the source directory: +The default path to the system performance database (System PerfDb) is `miopen/share/miopen/db/` +within the install location. The default path to the user performance database (User PerfDb) is +`~/.config/miopen/`. For development purposes, setting `BUILD_DEV` changes the default path to both +database files to the source directory: ```shell -cmake -DMIOPEN_BACKEND=OpenCL -DBUILD_DEV=On .. +cmake -DMIOPEN_BACKEND=HIP -DBUILD_DEV=On .. ``` -Database paths can be explicitly customized by means of `MIOPEN_SYSTEM_DB_PATH` (System PerfDb) and `MIOPEN_USER_DB_PATH` (User PerfDb) cmake variables. +Database paths can be explicitly customized using the `MIOPEN_SYSTEM_DB_PATH` (System PerfDb) +and `MIOPEN_USER_DB_PATH` (User PerfDb) CMake variables. -More information about the performance database can be found [here](https://rocm.docs.amd.com/projects/MIOpen/en/latest/perfdatabase.html). +To learn more, refer to the +[performance database](https://rocm.docs.amd.com/projects/MIOpen/en/latest/perfdatabase.html) +documentation. -### Persistent Program Cache +### Persistent program cache -MIOpen by default caches the device programs in the location `~/.cache/miopen/`. In the cache directory there exists a directory for each version of MIOpen. Users can change the location of the cache directory during configuration using the flag `-DMIOPEN_CACHE_DIR=`. +By default, MIOpen caches device programs in the `~/.cache/miopen/` directory. Within the cache +directory, there is a directory for each version of MIOpen. You can change the location of the cache +directory during configuration using the `-DMIOPEN_CACHE_DIR=` flag. -Users can also disable the cache during runtime using the environmental variable set as `MIOPEN_DISABLE_CACHE=1`. +You can also disable the cache during runtime using the `MIOPEN_DISABLE_CACHE=1` environmental +variable. #### For MIOpen version 2.3 and earlier -If the compiler changes, or the user modifies the kernels then the cache must be deleted for the MIOpen version in use; e.g., `rm -rf ~/.cache/miopen/`. More information about the cache can be found [here](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html). +If the compiler changes, or you modify the kernels, then you must delete the cache for the MIOpen +version in use (e.g., `rm -rf ~/.cache/miopen/`). You can find more +information in the [cache](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html) +documentation. #### For MIOpen version 2.4 and later -MIOpen's kernel cache directory is versioned so that users' cached kernels will not collide when upgrading from earlier version. +MIOpen's kernel cache directory is versioned so that your cached kernels won't collide when upgrading +from an earlier version. -### Changing the cmake configuration +### Changing the CMake configuration -The configuration can be changed after running cmake by using `ccmake`: +The configuration can be changed after running CMake (using `ccmake`): -`ccmake ..` **OR** `cmake-gui`: `cmake-gui ..` +`ccmake ..` **or** `cmake-gui`: `cmake-gui ..` -The `ccmake` program can be downloaded as the Linux package `cmake-curses-gui`, but is not available on windows. +The `ccmake` program can be downloaded as a Linux package (`cmake-curses-gui`), but is not available +on Windows. ## Building the library -The library can be built, from the `build` directory using the 'Release' configuration: +You can build the library from the `build` directory using the 'Release' configuration: -`cmake --build . --config Release` **OR** `make` +`cmake --build . --config Release` **or** `make` -And can be installed by using the 'install' target: +You can install it using the 'install' target: -`cmake --build . --config Release --target install` **OR** `make install` +`cmake --build . --config Release --target install` **or** `make install` -This will install the library to the `CMAKE_INSTALL_PREFIX` path that was set. +This installs the library to the `CMAKE_INSTALL_PREFIX` path that you specified. ## Building the driver -MIOpen provides an [application-driver](https://github.com/ROCm/MIOpen/tree/master/driver) which can be used to execute any one particular layer in isolation and measure performance and verification of the library. +MIOpen provides an [application-driver](https://github.com/ROCm/MIOpen/tree/master/driver) that +you can use to run any layer in isolation, and measure library performance and verification. -The driver can be built using the `MIOpenDriver` target: +You can build the driver using the `MIOpenDriver` target: -` cmake --build . --config Release --target MIOpenDriver ` **OR** ` make MIOpenDriver ` +` cmake --build . --config Release --target MIOpenDriver ` **or** ` make MIOpenDriver ` -Documentation on how to run the driver is [here](https://github.com/ROCm/MIOpen/blob/develop/driver/README.md). +To learn more, refer to the [driver](https://rocm.docs.amd.com/projects/MIOpen/en/latest/driver.html) +documentation. ## Running the tests -The tests can be run by using the 'check' target: +You can run tests using the 'check' target: ` cmake --build . --config Release --target check ` **OR** ` make check ` -A single test can be built and ran, by doing: +To build and run a single test, use the following code: ```shell cmake --build . --config Release --target test_tensor @@ -229,37 +274,40 @@ cmake --build . --config Release --target test_tensor ## Formatting the code -All the code is formatted using clang-format. To format a file, use: +All the code is formatted using `clang-format`. To format a file, use: ```shell clang-format-10 -style=file -i ``` -Also, githooks can be installed to format the code per-commit: +To format the code per commit, you can install githooks: ```shell ./.githooks/install ``` -## Storing large file using Git LFS +## Storing large file using Git Large File Storage -Git Large File Storage (LFS) replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server. In MIOpen, we use git LFS to store the large files, such as the kernel database files (*.kdb) which are normally > 0.5GB. Steps: +Git Large File Storage (LFS) replaces large files, such as audio samples, videos, datasets, and graphics +with text pointers inside Git, while storing the file contents on a remote server. In MIOpen, we use Gi +LFS to store our large files, such as our kernel database files (*.kdb) that are normally > 0.5 GB. -Git LFS can be installed and set up by: +You can install Git LFS using the following code: ```shell sudo apt install git-lfs git lfs install ``` -In the Git repository that you want to use Git LFS, track the file type that you's like by (if the file type has been tracked, this step can be skipped): +In the Git repository where you want to use Git LFS, track the file type using the following code (if the +file type has already been tracked, you can skip this step): ```shell git lfs track "*.file_type" git add .gitattributes ``` -Pull all or a single large file that you would like to update by: +You can pull all or a single large file using: ```shell git lfs pull --exclude= @@ -267,7 +315,7 @@ or git lfs pull --exclude= --include "filename" ``` -Update the large files and push to the github by: +Update the large files and push to GitHub using: ```shell git add my_large_files @@ -277,7 +325,7 @@ git push ## Installing the dependencies manually -If Ubuntu v16 is used then the `Boost` packages can also be installed by: +If you're using Ubuntu v16, you can install the `Boost` packages using: ```shell sudo apt-get install libboost-dev @@ -285,34 +333,32 @@ sudo apt-get install libboost-system-dev sudo apt-get install libboost-filesystem-dev ``` -*Note:* MIOpen by default will attempt to build with Boost statically linked libraries. If it is needed, the user can build with dynamically linked Boost libraries by using this flag during the configruation stage: - -```shell --DBoost_USE_STATIC_LIBS=Off -``` - -however, this is not recommended. +>[!NOTE] +>By default, MIOpen attempts to build with Boost statically linked libraries. If required, you can build +with dynamically linked Boost libraries using the `-DBoost_USE_STATIC_LIBS=Off` flag during the +configuration stage. However, this is not recommended. -The `half` header needs to be installed from [here](http://half.sourceforge.net/). +You must install the `half` header from the [half website](http://half.sourceforge.net/). -## Using docker +## Using Docker -The easiest way is to use docker. You can build the top-level docker file: +The easiest way to build MIOpen is via Docker. You can build the top-level Docker file using: ```shell docker build -t miopen-image . ``` -Then to enter the development environment use `docker run`, for example: +Then, to enter the development environment, use `docker run`. For example: ```shell docker run -it -v $HOME:/data --privileged --rm --device=/dev/kfd --device /dev/dri:/dev/dri:rw --volume /dev/dri:/dev/dri:rw -v /var/lib/docker/:/var/lib/docker --group-add video --cap-add=SYS_PTRACE --security-opt seccomp=unconfined miopen-image ``` -Prebuilt docker images can be found on [ROCm's public docker hub here](https://hub.docker.com/r/rocm/miopen/tags). +You can find prebuilt Docker images on +[ROCm's public Docker Hub](https://hub.docker.com/r/rocm/miopen/tags). ## Porting from cuDNN to MIOpen -The [porting -guide](https://github.com/ROCm/MIOpen/tree/develop/docs/MIOpen_Porting_Guide.md) -highlights the key differences between the current cuDNN and MIOpen APIs. +Our +[porting guide](https://rocm.docs.amd.com/projects/MIOpen/en/latest/MIOpen_Porting_Guide.html) +highlights the key differences between cuDNN and MIOpen APIs. diff --git a/cmake/TargetFlags.cmake b/cmake/TargetFlags.cmake index ede876fb55..bbccbe9d37 100644 --- a/cmake/TargetFlags.cmake +++ b/cmake/TargetFlags.cmake @@ -76,7 +76,9 @@ function(target_flags FLAGS TARGET) set(_flags) append_flags(_flags ${TARGET} "INTERFACE_COMPILE_OPTIONS" "") append_flags(_flags ${TARGET} "INTERFACE_COMPILE_DEFINITIONS" "-D") +if(MIOPEN_HIP_COMPILER_USE_SYSTEM_INCLUDE_DIRECTORIES) append_flags(_flags ${TARGET} "INTERFACE_INCLUDE_DIRECTORIES" "-isystem ") +endif() append_flags(_flags ${TARGET} "INTERFACE_LINK_DIRECTORIES" "-L ") append_flags(_flags ${TARGET} "INTERFACE_LINK_OPTIONS" "") append_link_flags(_flags ${TARGET} "INTERFACE_LINK_LIBRARIES" "") diff --git a/dev-requirements.txt b/dev-requirements.txt index d129a1defc..f5082a1a6a 100755 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,4 @@ -ROCmSoftwarePlatform/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8 +ROCm/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8 -f requirements.txt danmar/cppcheck@2.12.1 google/googletest@v1.14.0 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000000..69bd2d0d07 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,10 @@ +# documentation artifacts +_build/ +_images/ +_static/ +_templates/ +_toc.yml +docBin/ +_doxygen/ +doxygen/html/ +doxygen/xml/ diff --git a/docs/DebugAndLogging.md b/docs/DebugAndLogging.md deleted file mode 100644 index f862274ac5..0000000000 --- a/docs/DebugAndLogging.md +++ /dev/null @@ -1,248 +0,0 @@ -Debugging and Logging -===================== - -## Logging - -All logging messages output to standard error stream (`stderr`). The following environment variables can be used to control logging: - -* `MIOPEN_ENABLE_LOGGING` - Enables printing the basic layer by layer MIOpen API call information with actual parameters (configurations). Important for debugging. Disabled by default. - -* `MIOPEN_ENABLE_LOGGING_CMD` - A user can use this environmental variable to output the associated `MIOpenDriver` command line(s) onto console. Disabled by default. - -> **_NOTE 1:_ These two and other two-state ("boolean") environment variables can be set to the following values:** -> ``` -> 1, on, yes, true, enable, enabled - to enable feature -> 0, off, no, false, disable, disabled - to disable feature -> ``` - -* `MIOPEN_LOG_LEVEL` - In addition to API call information and driver commands, MIOpen prints various information related to the progress of its internal operations. This information can be useful both for debugging and for understanding the principles of operation of the library. The `MIOPEN_LOG_LEVEL` environment variable controls the verbosity of these messages. Allowed values are: - * 0 - Default. Works as level 4 for Release builds, level 5 for Debug builds. - * 1 - Quiet. No logging messages. - * 2 - Fatal errors only (not used yet). - * 3 - Errors and fatals. - * 4 - All errors and warnings. - * 5 - Info. All the above plus information for debugging purposes. - * 6 - Detailed info. All the above plus more detailed information for debugging. - * 7 - Trace: the most detailed debugging info plus all above. - -> **_NOTE 2:_ When asking for technical support, please include the console log obtained with the following settings:** -> ``` -> export MIOPEN_ENABLE_LOGGING=1 -> export MIOPEN_ENABLE_LOGGING_CMD=1 -> export MIOPEN_LOG_LEVEL=6 -> ``` - -* `MIOPEN_ENABLE_LOGGING_MPMT` - When enabled, each log line is prefixed with information which allows the user to identify records printed from different processes and/or threads. Useful for debugging multi-process/multi-threaded apps. - -* `MIOPEN_ENABLE_LOGGING_ELAPSED_TIME` - Adds a timestamp to each log line. Indicates the time elapsed since the previous log message, in milliseconds. - -## Layer Filtering - -The following list of environment variables allow for enabling/disabling various kinds of kernels and algorithms. This can be helpful for both debugging MIOpen and integration with frameworks. - -> **_NOTE 3:_ These variables can be set to the following values:** -> ``` -> 1, yes, true, enable, enabled - to enable kernels/algorithm -> 0, no, false, disable, disabled - to disable kernels/algorithm -> ``` - -If a variable is not set, then MIOpen behaves as if it is set to `enabled`, unless otherwise specified. So all kinds of kernels/algorithms are enabled by default and the below variables can be used for disabling them. - -> **_WARNING:_** **When the library is used with layer filtering, the results of `Find()` calls become narrower than during normal operation. This means that relevant find-db entries would not include some solutions that normally should be there.** **_Therefore the subsequent Immediate mode `Get()` calls may return incomplete information or even run into Fallback path._** - -In order to rehabilitate the Immediate mode, the user can: -- Re-enable all solvers and re-run the same `Find()` calls that have been run before, -- Or, completely remove the User find-db. - -### Filtering by algorithm - -These variables control the sets (families) of convolution Solutions. For example, Direct algorithm is implemented in several Solutions that use OpenCL, GCN assembly etc. The corresponding variable can disable them all. -* `MIOPEN_DEBUG_CONV_FFT` - FFT convolution algorithm. -* `MIOPEN_DEBUG_CONV_DIRECT` - Direct convolution algorithm. -* `MIOPEN_DEBUG_CONV_GEMM` - GEMM convolution algorithm. -* `MIOPEN_DEBUG_CONV_WINOGRAD` - Winograd convolution algorithm. -* `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM` - Implicit GEMM convolution algorithm. - -### Filtering by build method - -* `MIOPEN_DEBUG_GCN_ASM_KERNELS` - Kernels written in assembly language. Currently these used in many convolutions (some Direct solvers, Winograd kernels, fused convolutions), batch normalization. -* `MIOPEN_DEBUG_HIP_KERNELS` - Convoluton kernels written in HIP (today, all these implement ImplicitGemm algorithm). -* `MIOPEN_DEBUG_OPENCL_CONVOLUTIONS` - Convolution kernels written in OpenCL (note that _only_ convolutions affected). -* `MIOPEN_DEBUG_AMD_ROCM_PRECOMPILED_BINARIES` - Binary kernels. Right now the library does not use binaries. - -### Filtering out all Solutions except one - -* `MIOPEN_DEBUG_FIND_ONLY_SOLVER=solution_id`, where `solution_id` should be either numeric or string identifier of some Solution. Directly affects only `Find()` calls _(however there is some indirect connection to Immediate mode; please see the "Warning" above.)_ - - If `solution_id` denotes some applicable Solution, then only that Solution will be found (plus GEMM and FFT, if these applicable, see _Note 4_). - - Else, if `solution_id` is valid but not applicable, then `Find()` would fail with all algorithms (again, except GEMM and FFT, see _Note 4_) - - Otherwise the `solution_id` is invalid (i.e. it doesn't match any existing Solution), and the `Find()` call would fail. - -> **_NOTE 4:_** This env. variable does not affect the "gemm" and "fft" solutions. For now, GEMM and FFT can be disabled only at algorithm level (see above). - -### Filtering the Solutions on individual basis - -Some of the Solutions have individual controls available. These affect both Find and Immediate modes. _Note the "Warning" above._ - -Direct Solutions: -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_3X3U` - `ConvAsm3x3U`. -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_1X1U` - `ConvAsm1x1U`. -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_1X1UV2` - `ConvAsm1x1UV2`. -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_5X10U2V2` - `ConvAsm5x10u2v2f1`, `ConvAsm5x10u2v2b1`. -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_7X7C3H224W224` - `ConvAsm7x7c3h224w224k64u2v2p3q3f1`. -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_WRW3X3` - `ConvAsmBwdWrW3x3`. -* `MIOPEN_DEBUG_CONV_DIRECT_ASM_WRW1X1` - `ConvAsmBwdWrW1x1`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_FWD11X11` - `ConvOclDirectFwd11x11`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_FWDGEN` - `ConvOclDirectFwdGen`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_FWD` - `ConvOclDirectFwd`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_FWD1X1` - `ConvOclDirectFwd1x1`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_WRW2` - `ConvOclBwdWrW2` (where n = `{1,2,4,8,16}`), and `ConvOclBwdWrW2NonTunable`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_WRW53` - `ConvOclBwdWrW53`. -* `MIOPEN_DEBUG_CONV_DIRECT_OCL_WRW1X1` - `ConvOclBwdWrW1x1` - -Winograd Solutions: -* `MIOPEN_DEBUG_AMD_WINOGRAD_3X3` - `ConvBinWinograd3x3U`, FP32 Winograd Fwd/Bwd, filter size fixed to 3x3. -* `MIOPEN_DEBUG_AMD_WINOGRAD_RXS` - `ConvBinWinogradRxS`, FP32/FP16 F(3,3) Fwd/Bwd and FP32 F(3,2) WrW Winograd. Subsets: - * `MIOPEN_DEBUG_AMD_WINOGRAD_RXS_WRW` - FP32 F(3,2) WrW convolutions only. - * `MIOPEN_DEBUG_AMD_WINOGRAD_RXS_FWD_BWD` - FP32/FP16 F(3,3) Fwd/Bwd. -* `MIOPEN_DEBUG_AMD_WINOGRAD_RXS_F3X2` - `ConvBinWinogradRxSf3x2`, FP32/FP16 Fwd/Bwd F(3,2) Winograd. -* `MIOPEN_DEBUG_AMD_WINOGRAD_RXS_F2X3` - `ConvBinWinogradRxSf2x3`, FP32/FP16 Fwd/Bwd F(2,3) Winograd, serves group convolutions only. -* `MIOPEN_DEBUG_AMD_WINOGRAD_RXS_F2X3_G1` - `ConvBinWinogradRxSf2x3g1`, FP32/FP16 Fwd/Bwd F(2,3) Winograd, for non-group convolutions. - -* Multi-pass Winograd: - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X2` - `ConvWinograd3x3MultipassWrW<3-2>`, WrW F(3,2), stride 2 only. - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X3` - `ConvWinograd3x3MultipassWrW<3-3>`, WrW F(3,3), stride 2 only. - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X4` - `ConvWinograd3x3MultipassWrW<3-4>`, WrW F(3,4). - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X5` - `ConvWinograd3x3MultipassWrW<3-5>`, WrW F(3,5). - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X6` - `ConvWinograd3x3MultipassWrW<3-6>`, WrW F(3,6). - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F5X3` - `ConvWinograd3x3MultipassWrW<5-3>`, WrW F(5,3). - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F5X4` - `ConvWinograd3x3MultipassWrW<5-4>`, WrW F(5,4). - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F7X2`: - * `ConvWinograd3x3MultipassWrW<7-2>`, WrW F(7,2) - * `ConvWinograd3x3MultipassWrW<7-2-1-1>`, WrW F(7x1,2x1) - * `ConvWinograd3x3MultipassWrW<1-1-7-2>`, WrW F(1x7,1x2) - * `MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F7X3`: - * `ConvWinograd3x3MultipassWrW<7-3>`, WrW F(7,3) - * `ConvWinograd3x3MultipassWrW<7-3-1-1>`, WrW F(7x1,3x1) - * `ConvWinograd3x3MultipassWrW<1-1-7-3>`, WrW F(1x7,1x3) - * `MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F2X3` - `ConvMPBidirectWinograd<2-3>`, FWD/BWD F(2,3) - * `MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F3X3` - `ConvMPBidirectWinograd<3-3>`, FWD/BWD F(3,3) - * `MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F4X3` - `ConvMPBidirectWinograd<4-3>`, FWD/BWD F(4,3) - * `MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F5X3` - `ConvMPBidirectWinograd<5-3>`, FWD/BWD F(5,3) - * `MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F6X3` - `ConvMPBidirectWinograd<6-3>`, FWD/BWD F(6,3) - * `MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F2X3` - `ConvMPBidirectWinograd_xdlops<2-3>`, FWD/BWD F(2,3) - * `MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F3X3` - `ConvMPBidirectWinograd_xdlops<3-3>`, FWD/BWD F(3,3) - * `MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F4X3` - `ConvMPBidirectWinograd_xdlops<4-3>`, FWD/BWD F(4,3) - * `MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F5X3` - `ConvMPBidirectWinograd_xdlops<5-3>`, FWD/BWD F(5,3) - * `MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F6X3` - `ConvMPBidirectWinograd_xdlops<6-3>`, FWD/BWD F(6,3) - * `MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_EXPEREMENTAL_FP16_TRANSFORM - `ConvMPBidirectWinograd*`, FWD/BWD FP16 experemental mode. Disabled by default. This mode is experimental. Use it at your own risk. -* `MIOPEN_DEBUG_AMD_FUSED_WINOGRAD` - Fused FP32 F(3,3) Winograd, variable filter size. - -Implicit GEMM Solutions: -* ASM Implicit GEMM - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_FWD_V4R1` - `ConvAsmImplicitGemmV4R1DynamicFwd` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_FWD_V4R1_1X1` - `ConvAsmImplicitGemmV4R1DynamicFwd_1x1` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_BWD_V4R1` - `ConvAsmImplicitGemmV4R1DynamicBwd` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_WRW_V4R1` - `ConvAsmImplicitGemmV4R1DynamicWrw` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_FWD_GTC_XDLOPS` - `ConvAsmImplicitGemmGTCDynamicFwdXdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_BWD_GTC_XDLOPS` - `ConvAsmImplicitGemmGTCDynamicBwdXdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_WRW_GTC_XDLOPS` - `ConvAsmImplicitGemmGTCDynamicWrwXdlops` -* HIP Implicit GEMM - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R1` - `ConvHipImplicitGemmV4R1Fwd` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R4` - `ConvHipImplicitGemmV4R4Fwd` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V1R1` - `ConvHipImplicitGemmBwdDataV1R1` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V4R1` - `ConvHipImplicitGemmBwdDataV4R1` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R1` - `ConvHipImplicitGemmV4R1WrW` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R4` - `ConvHipImplicitGemmV4R4WrW` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R4_XDLOPS` - `ConvHipImplicitGemmForwardV4R4Xdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R5_XDLOPS` - `ConvHipImplicitGemmForwardV4R5Xdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V1R1_XDLOPS` - `ConvHipImplicitGemmBwdDataV1R1Xdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V4R1_XDLOPS` - `ConvHipImplicitGemmBwdDataV4R1Xdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R4_XDLOPS` - `ConvHipImplicitGemmWrwV4R4Xdlops` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R4_PADDED_GEMM_XDLOPS` - `ConvHipImplicitGemmForwardV4R4Xdlops_Padded_Gemm` - * `MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R4_PADDED_GEMM_XDLOPS` - `ConvHipImplicitGemmWrwV4R4Xdlops_Padded_Gemm` - -## rocBlas Logging and Behavior -The `ROCBLAS_LAYER` environmental variable can be set to output GEMM information: -* `ROCBLAS_LAYER=` - is not set, there is no logging -* `ROCBLAS_LAYER=1` - is set to 1, then there is trace logging -* `ROCBLAS_LAYER=2` - is set to 2, then there is bench logging -* `ROCBLAS_LAYER=3` - is set to 3, then there is both trace and bench logging - -Additionally, the environment variable "MIOPEN_GEMM_ENFORCE_BACKEND" can be set to override default GEMM backend (Default GEMM backend is rocBLAS): - -* `MIOPEN_GEMM_ENFORCE_BACKEND=1`, use rocBLAS if enabled -* `MIOPEN_GEMM_ENFORCE_BACKEND=2`, reserved -* `MIOPEN_GEMM_ENFORCE_BACKEND=3`, no gemm will be called -* `MIOPEN_GEMM_ENFORCE_BACKEND=4`, reserved -* `MIOPEN_GEMM_ENFORCE_BACKEND=`, use default behavior - -To disable using rocBlas entirely, set the configuration flag `-DMIOPEN_USE_ROCBLAS=Off` during MIOpen configuration. - -More information on logging with rocBlas can be found [here](https://github.com/ROCm/rocBLAS/wiki/5.Logging). - - -## Numerical Checking - -MIOpen provides the environmental variable `MIOPEN_CHECK_NUMERICS` to allow users to debug potential numerical abnormalities. Setting this variable will scan all inputs and outputs of each kernel called and attempt to detect infinities (infs), not-a-number (NaN), or all zeros. The environment variable has several settings that will help with debugging: - -* `MIOPEN_CHECK_NUMERICS=0x01`: Fully informative, prints results from all checks to console -* `MIOPEN_CHECK_NUMERICS=0x02`: Warning information, prints results only if abnormality detected -* `MIOPEN_CHECK_NUMERICS=0x04`: Throw error on detection, MIOpen execute MIOPEN_THROW on abnormal result -* `MIOPEN_CHECK_NUMERICS=0x08`: Abort on abnormal result, this will allow users to drop into a debugging session -* `MIOPEN_CHECK_NUMERICS=0x10`: Print stats, this will compute and print mean/absmean/min/max (note, this is much slower) - - -## Controlling Parallel Compilation - -MIOpen's Convolution Find() calls will compile and benchmark a set of `solvers` contained in `miopenConvAlgoPerf_t` this is done in parallel per `miopenConvAlgorithm_t`. Parallelism per algorithm is set to 20 threads. Typically there are far fewer threads spawned due to the limited number of kernels under any given algorithm. The level of parallelism can be controlled using the environment variable `MIOPEN_COMPILE_PARALLEL_LEVEL`. - -For example, to disable multi-threaded compilation: -``` -export MIOPEN_COMPILE_PARALLEL_LEVEL=1 -``` - - -## Experimental controls - -> **_NOTE 5: Using experimental controls may result in:_** -> * Performance drops -> * Computation inaccuracies -> * Run-time errors -> * Other kinds of unexpected behavior -> -> **_It is strongly recommended to use them only with the explicit permission or request of the library developers._** - -### Code Object (CO) version selection (EXPERIMENTAL) - -Different ROCm versions use Code Object files of different versions (or, in other words, formats). The library uses suitable version automatically. The following variables allow for experimenting and triaging possible problems related to CO version: -* `MIOPEN_DEBUG_AMD_ROCM_METADATA_ENFORCE` - Affects kernels written in GCN assembly language. - * `0` or unset - Automatically detect the required CO version and assemble to that version. This is the default. - * `1` - Do not auto-detect Code Object version, always assemble v2 Code Objects. - * `2` - Behave as if both CO v2 and v3 are supported (see `MIOPEN_DEBUG_AMD_ROCM_METADATA_PREFER_OLDER`). - * `3` - Always assemble v3 Code Objects. -* `MIOPEN_DEBUG_AMD_ROCM_METADATA_PREFER_OLDER` - This variable affects only assembly kernels, and only when ROCm supports both CO v2 and CO v3 (like ROCm 2.10). By default, the newer format is used (CO v3). When this variable is _enabled_, the behavior is reversed. -* `MIOPEN_DEBUG_OPENCL_ENFORCE_CODE_OBJECT_VERSION` - Enforces Code Object format for OpenCL kernels. Works with HIP backend only (`cmake ... -DMIOPEN_BACKEND=HIP...`). - * Unset - Automatically detect the required CO version. This is the default. - * `2` - Always build to CO v2. - * `3` - Always build to CO v3. - * `4` - Always build to CO v4. - -### Winograd Multi-pass Maximum Workspace throttling - -`MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_WORKSPACE_MAX` - `ConvWinograd3x3MultipassWrW`, WrW -`MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_WORKSPACE_MAX` - `ConvMPBidirectWinograd*`, FWD BWD - -Syntax of value: -* decimal or hex (with `0x` prefix) value that should fit into 64-bit unsigned integer. -* If syntax is violated, then the behavior is unspecified. - -Semantics: -* Sets the **_limit_** (max allowed workspace size) for Multi-pass (MP) Winograd Solutions, in bytes. -* Affects all MP Winograd Solutions. If a Solution needs more workspace than the limit, then it does not apply. -* If unset, then _the default_ limit is used. Current default is `2000000000` (~1.862 GiB) for gfx900 and gfx906/60 (or less CUs). No default limit is set for other GPUs. -* Special values: -``` - 0 - Use the default limit, as if the variable is unset. - 1 - Completely prohibit the use of workspace. --1 - Remove the default limit. -``` diff --git a/docs/Getting_Started_FusionAPI.md b/docs/Getting_Started_FusionAPI.md deleted file mode 100644 index 44140cb0ef..0000000000 --- a/docs/Getting_Started_FusionAPI.md +++ /dev/null @@ -1,209 +0,0 @@ -Fusion API: Getting Started -=========================== -## Introduction -Increasing depth of deep learning networks necessitate the need for novel mechanisms to improve performance on GPUs. One mechanism to achieve higher efficiency is to _fuse_ separate kernels into a single kernel to reduce off-chip memory access and avoid kernel launch overhead. This document outlines the addition of a Fusion API to the MIOpen library. The fusion API would allow users to specify operators that they wants to fuse in a single kernel, compile it and then launch the kernel. While not all combinations might be supported by the library, the API is flexible enough to allow the specification of many operations in any order from a finite set of supported operations. The API provides a mechanism to report unsupported combinations. - -A complete example of the Fusion API in the context of MIOpen is given [here](https://github.com/ROCm/MIOpenExamples/tree/master/fusion). We will use code from the example project as we go along. The example project creates a fusion plan to merge the convolution, bias and activation operations. For a list of supported fusion operations and associated constraints please refer to the [Supported Fusions](#supported-fusions) section. The example depicts bare-bones code without any error checking or even populating the tensors with meaningful data in the interest of simplicity. - -The following list outlines the steps required - -- Create a fusion plan -- Create and add the convolution, bias and activation operators -- Compile the Fusion Plan -- Set the runtime arguments for each operator -- Execute the fusion plan -- Cleanup - -The above steps assume that an MIOpen handle object has already been initialized. Moreover, the order in which operators are created is important, since it represents the order of operations on the data itself. Therefore a fusion plan with convolution created before activation is a different fusion plan as opposed to if activation was added before convolution. - -The following sections further elaborate the above steps as well as give code examples to make these ideas concrete. - -### Intended Audience -The primary consumers of the fusion API are high level frameworks such as TensorFlow/XLA or PyTorch etc. - -## Create a Fusion Plan -A **Fusion Plan** is the data structure which holds all the metadata about the users fusion intent as well as logic to **Compile** and **Execute** a fusion plan. As mentioned earlier, a fusion plan holds the order in which different opertions would be applied on the data, but it also specifies the _axis_ of fusion as well. Currently only **vertical** (sequential) fusions are supported implying the flow of data between operations is sequential. - -A fusion plan is created using the API call `miopenCreateFusionPlan` with the signature: - -```cpp -miopenStatus_t -miopenCreateFusionPlan(miopenFusionPlanDescriptor_t* fusePlanDesc, -const miopenFusionDirection_t fuseDirection,const miopenTensorDescriptor_t inputDesc); -``` - -The *input tensor descriptor* specifies the geometry of the incoming data. Since the data geometry of the intermediate operations can be derived from the *input tensor descriptor*, therefore only the *input tensor descriptor* is required for the fusion plan and not for the individual operations. In our fusion example the following lines of code accomplish this: -```cpp -miopenCreateFusionPlan(&fusePlanDesc, miopenVerticalFusion, input.desc); -``` -Where `fusePlanDesc` is an object of type `miopenFusionPlanDescriptor_t` and `input.desc` is the `miopenTensorDescriptor_t` object. - -## Create and add Operators -The fusion API introduces the notion of **operators** which represent different operations that are intended to be fused together by the API consumer. Currently, the API supports the following operators: - -* Convolution Forward -* Activation Forward -* BatchNorm Inference -* Bias Forward - -Notice that _Bias_ is a separate operator, although it is typically only used with convolution. This list is expected to grow as support for more operators is added to the API, moreover, operators for backward passes are in the works as well. - -The fusion API provides calls for the creation of the supported operators, here we would describe the process for the convolution operator, details for other operators may be found in the [miopen header file](https://rocm.docs.amd.com/projects/MIOpen/en/latest/fusion.html) - -Once the fusion plan descriptor is created, two or more operators can be added to it by using the individual operator creation API calls. Creation of an operator might fail if the API does not support the fusion of the operations being added and report back immediately to the user. For our example we need to add the Convolution, Bias and Activation operations to our freshly minted fusion plan. This is done using the following calls for the Convolution, Bias and Activation operations respectively: - -```cpp -miopenStatus_t -miopenCreateOpConvForward(miopenFusionPlanDescriptor_t fusePlanDesc, - miopenFusionOpDescriptor_t* convOp, - miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t wDesc); -miopenStatus_t -miopenCreateOpBiasForward(miopenFusionPlanDescriptor_t fusePlanDesc, - miopenFusionOpDescriptor_t* biasOp, - const miopenTensorDescriptor_t bDesc); - -miopenStatus_t -miopenCreateOpActivationForward(miopenFusionPlanDescriptor_t fusePlanDesc, - miopenFusionOpDescriptor_t* activOp, - miopenActivationMode_t mode); -``` - -The following lines in the fusion example project use these API calls to create and insert the operators in the fusion plan: - -```cpp -miopenCreateOpConvForward(fusePlanDesc, &convoOp, conv_desc, weights.desc); -miopenCreateOpBiasForward(fusePlanDesc, &biasOp, bias.desc); -miopenCreateOpActivationForward(fusePlanDesc, &activOp, miopenActivationRELU); -``` - -It may be noted that `conv_desc` is the regular MIOpen Convolution descriptor and is created in the standard way before it is referenced here. For more details on creating and setting the convolution descriptor please refer to the example code as well as the [MIOpen documentation](https://rocm.docs.amd.com/projects/MIOpen/en/latest/convolution.html). In the above snippet `weights.desc` refers to the `miopenTensorDescriptor_t` for the convolution operations and `bias.desc` refers to the object of the same type for the bias operation. The order of insertion of operators indicates the order in which the operations would be performed on the data. Therefore, the above code implies that the convolution operation would be the first operation to execute on the incoming data, followed by the bias and activation operations. - -During this process, it is important that the returned codes be checked to make sure that the operations as well as their order is supported. The operator insertion might fail for a number of reasons such as unsupported sequence of operations, unsupported dimensions of the input or in case of convolution unsupported dimensions for the filters. In the above example, these aspects are ignored for the sake of simplicity. - -## Compile the Fusion Plan - -Following the operator addition, the user would compile the fusion plan, to populate the MIOpen kernel cache with the fused kernel and make it ready for execution. The API call that accomplishes this is: - -```cpp -miopenStatus_t -miopenCompileFusionPlan(miopenHandle_t handle, miopenFusionPlanDescriptor_t fusePlanDesc); -``` - -The corresponding code snippet in the example is as follows: - -```cpp -auto status = miopenCompileFusionPlan(mio::handle(), fusePlanDesc); -if (status != miopenStatusSuccess) { -return -1; -} -``` -In order to compile the fusion plan, the user is assumed to have acquired an MIOpen handle object, in the example code above this is accomplished using the `mio::handle()` helper function. While a fusion plan itself is not bound to a MIOpen handle object, it would however need to be recompiled for each handle separately. It may be noted that compilation of a fusion plan might fail for a number of reasons, moreover it is not assured that a fused version of the kernel would offer any performance improvement over the separately run kernels. - -Compiling a fusion plan is a costly operation in terms of run-time. Therefore, it is recommended that a fusion plan should only be compiled once and may be reused for execution with different runtime parameters as described in the next section. - -## Set the runtime arguments - -While the underlying MIOpen descriptor of the fusion operator specifies the data geometry and parameters, the fusion plan still needs access to the data to execute a successfully compiled fusion plan. The arguments mechanism in the Fusion API provides such data before a fusion plan may be executed. For example the convolution operator requires *weights* to carry out the convolution computation, a bias operator requires the actual bias values etc. Therefore, before a fusion plan may be executed, arguments required by each fusion operator need to be specified. To begin, we create the `miopenOperatorArgs_t` object using: - -```cpp -miopenStatus_t miopenCreateOperatorArgs(miopenOperatorArgs_t* args); -``` - -Once created, runtime arguments for each operation may be set. In our running example, the forward convolution operator requires the convolution weights argument which is supplied using the API call: - -```cpp -miopenStatus_t -miopenSetOpArgsConvForward(miopenOperatorArgs_t args, - const miopenFusionOpDescriptor_t convOp, - const void* alpha, - const void* beta, - const void* w); -``` - -Similarly the parameters for bias and activation are given by: - -```cpp -miopenStatus_t miopenSetOpArgsBiasForward(miopenOperatorArgs_t args, - const miopenFusionOpDescriptor_t biasOp, - const void* alpha, - const void* beta, - const void* bias); - -miopenStatus_t miopenSetOpArgsActivForward(miopenOperatorArgs_t args, - const miopenFusionOpDescriptor_t activOp, - const void* alpha, - const void* beta, - double activAlpha, - double activBeta, - double activGamma); -``` - -In our example code, we set the arguments for the operations as follows: - -```cpp -miopenSetOpArgsConvForward(fusionArgs, convoOp, &alpha, &beta, weights.data); -miopenSetOpArgsActivForward(fusionArgs, activOp, &alpha, &beta, activ_alpha, - activ_beta, activ_gamma); -miopenSetOpArgsBiasForward(fusionArgs, biasOp, &alpha, &beta, bias.data); -``` - -This separation between the fusion plan and the arguments required by each operator allows better reuse of the fusion plan with different arguments as well as avoids the necessity of recompiling the fusion plan to run the same combination of operators with different arguments. - -As mentioned in the section [Compile the Fusion Plan](#compile-the-fusion-plan) earlier, the compilation step for a fusion plan might be costly, therefore a fusion plan should only be compiled once in its lifetime. A fusion plan needs not be recompiled if the input desciptor or any of the parameters to the `miopenCreateOp*` API calls are different, otherwise a compiled fusion plan may be reused again and again with a different set of arguments. In our example this is demonstrated in lines 77 - 85 of `main.cpp`. - -## Execute a Fusion Plan - -Once the fusion plan has been compiled and arguments set for each operator, it may be executed with the API call given below passing it the actual data to be processed. - -```cpp -miopenStatus_t -miopenExecuteFusionPlan(const miopenHandle_t handle, - const miopenFusionPlanDescriptor_t fusePlanDesc, - const miopenTensorDescriptor_t inputDesc, - const void* input, - const miopenTensorDescriptor_t outputDesc, - void* output, - miopenOperatorArgs_t args); -``` - -The following code snippet in the example accomplishes the fusion plan execution: - -```cpp -miopenExecuteFusionPlan(mio::handle(), fusePlanDesc, input.desc, input.data, - output.desc, output.data, fusionArgs); -``` - -It may be noted that it is an error to attempt to execute a fusion plan that is either not compiled or has been invalidated by changing the input tensor descriptor or any of the operation parameters. - - -## Cleanup -Once the application is done with the fusion plan, the fusion plan and the fusion args objects may be destroyed using the API calls: - -```cpp -miopenStatus_t miopenDestroyFusionPlan(miopenFusionPlanDescriptor_t fusePlanDesc); -``` -Once the fusion plan object is destroyed, all the operations created are destroyed automatically and do not need any special cleanup. - - -## Supported Fusions -The tables below outlines the supported fusions for fp32 and fp16 as well as any applicable constraints. **(C = convolution, B = bias, N = batch normalization, A = activation)** -Fusion Plans with grouped convolutions are not supported. - - -![Convolution based fp32 fusion](data/fp32fusions.png) - - -![Convolution based fp16 fusion](data/fp16fusions.png) - - -## Performance Comparison to Non-Fused Kernels - - -The following graph depicts the speedup gained for a fused Convolution+Bias+Activation over a non-fused version, all configurations have a batch size of 64: - -![CBA Graph](data/cba.png) - -Speedup obtained by fusing Batchnorm (spatial mode) with Activation are presented in the graph below: - -![Batchnorm activation fusion](data/na.png) diff --git a/docs/MI200AlternateImplementation.md b/docs/MI200AlternateImplementation.md deleted file mode 100644 index f5354eb0c0..0000000000 --- a/docs/MI200AlternateImplementation.md +++ /dev/null @@ -1,13 +0,0 @@ -## MI200 MFMA Behavior Specifics - -The MI200 MFMA_F16, MFMA_BF16 and MFMA_BF16_1K flush subnormal input/output data to zero. This behavior might affect the convolution operation in certain workloads due to the limited exponent range of the half-precision floating point datatypes. - -An alternate implementation for the half precision data-type is available in MIOpen which utilizes conversion instructions to utilizes the BFloat16 data-types larger exponent range, albeit with reduced accuracy. The following salients apply to this alternate implementation: - -* It is disabled by default in the Forward convolution operations. - -* It is enabled by default in the backward data and backward weights convolution operations. - -* The default MIOpen behaviors described above may be overridden using the `miopenSetConvolutionAttribute` API call and passing the convolution descriptor for the appropriate convolution operation and the `MIOPEN_CONVOLUTION_ATTRIB_FP16_ALT_IMPL` convolution attribute with a non-zero value to engage the alternate implementation. - -* The behavior might also be overridden using the `MIOPEN_DEBUG_CONVOLUTION_ATTRIB_FP16_ALT_IMPL` environment variable. The above variable when set to a value of `1` engages the alternate implementation while a value of `0` disables it. Keep in mind the environment variable impacts the convolution operation in all directions. \ No newline at end of file diff --git a/docs/MIOpen_Porting_Guide.md b/docs/MIOpen_Porting_Guide.md deleted file mode 100644 index 52ccf15434..0000000000 --- a/docs/MIOpen_Porting_Guide.md +++ /dev/null @@ -1,2063 +0,0 @@ - -# MIOpen Porting Guide - - -## The key differences between MIOpen and cuDNN: -* MIOpen only supports 4-D tensors in the NCHW and NHWC storage format. This means all the __“\*Nd\*”__ APIs in cuDNN do not have a corresponding API in MIOpen. -* MIOpen only supports __`float(fp32)`__ data-type. -* MIOpen supports __2D Convolutions__ and __3D Convolutions__. -* MIOpen only supports __2D Pooling__. -* Calling miopenFindConvolution*Algorithm() is *mandatory* before calling any Convolution API. -* Typical calling sequence for Convolution APIs for MIOpen is: - * miopenConvolution*GetWorkSpaceSize() // returns the workspace size required by Find() - * miopenFindConvolution*Algorithm() // returns performance info about various algorithms - * miopenConvolution*() -* MIOpen does not support __Preferences__ for convolutions. -* MIOpen does not support Softmax modes. MIOpen implements the __SOFTMAX_MODE_CHANNEL__ flavor. -* MIOpen does not support __Transform-Tensor__, __Dropout__, __RNNs__, and __Divisive Normalization__. - -



- -## Helpful MIOpen Environment Variables -`MIOPEN_ENABLE_LOGGING=1` – log all the MIOpen APIs called including the parameters passed to -those APIs. \ -`MIOPEN_DEBUG_AMD_ROCM_PRECOMPILED_BINARIES=0` – disable Winograd convolution -algorithm. \ -`MIOPEN_DEBUG_GCN_ASM_KERNELS=0` – disable hand-tuned asm. kernels for Direct convolution -algorithm. Fall-back to kernels written in high-level language. \ -`MIOPEN_DEBUG_CONV_FFT=0` – disable FFT convolution algorithm. \ -`MIOPEN_DEBUG_CONV_DIRECT=0` – disable Direct convolution algorithm. - -



- - -## API differences - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-Operation - - - -cuDNN API - - -MIOpen API -
- - -```c++ -cudnnStatus_t -cudnnCreate( - cudnnHandle_t *handle) -``` - - -```c++ -miopenStatus_t -miopenCreate( - miopenHandle_t *handle) -``` -
- - -```c++ -cudnnStatus_t -cudnnDestroy( - cudnnHandle_t handle) -``` - - -```c++ -miopenStatus_t -miopenDestroy( - miopenHandle_t handle) -``` -
-Handle - - -```c++ -cudnnStatus_t -cudnnSetStream( - cudnnHandle_t handle, - cudaStream_t streamId) -``` - - -```c++ -miopenStatus_t -miopenSetStream( - miopenHandle_t handle, - miopenAcceleratorQueue_t streamID) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetStream( - cudnnHandle_t handle, - cudaStream_t *streamId) -``` - - -```c++ -miopenStatus_t -miopenGetStream( - miopenHandle_t handle, - miopenAcceleratorQueue_t *streamID) -``` -
- - -```c++ -cudnnStatus_t -cudnnCreateTensorDescriptor( - cudnnTensorDescriptor_t *tensorDesc) -``` - - -```c++ -miopenStatus_t -miopenCreateTensorDescriptor( - miopenTensorDescriptor_t - *tensorDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnSetTensor4dDescriptor( - cudnnTensorDescriptor_t tensorDesc, - cudnnTensorFormat_t format, - cudnnDataType_t dataType, - int n, - int c, - int h, - int w) -``` - - -```c++ -// Only `NCHW` format is supported -miopenStatus_t miopenSet4dTensorDescriptor( - miopenTensorDescriptor_t tensorDesc, - miopenDataType_t dataType, - int n, - int c, - int h, - int w) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetTensor4dDescriptor( - cudnnTensorDescriptor_t tensorDesc, - cudnnDataType_t *dataType, - int *n, - int *c, - int *h, - int *w, - int *nStride, - int *cStride, - int *hStride, - int *wStride) -``` - - -```c++ -miopenStatus_t -miopenGet4dTensorDescriptor( - miopenTensorDescriptor_t tensorDesc, - miopenDataType_t *dataType, - int *n, - int *c, - int *h, - int *w, - int *nStride, - int *cStride, - int *hStride, - int *wStride) -``` -
-Tensor - - -```c++ -cudnnStatus_t -cudnnDestroyTensorDescriptor( - cudnnTensorDescriptor_t tensorDesc) -``` - - -```c++ -miopenStatus_t -miopenDestroyTensorDescriptor( - miopenTensorDescriptor_t tensorDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnAddTensor( - cudnnHandle_t handle, - const void *alpha, - const cudnnTensorDescriptor_t aDesc, - const void *A, - const void *beta, - const cudnnTensorDescriptor_t cDesc, - void *C) -``` - - -```c++ -//Set tensorOp to miopenOpTensorAdd -miopenStatus_t -miopenOpTensor( - miopenHandle_t handle, - miopenTensorOp_t tensorOp, - const void *alpha1, - constmiopenTensorDescriptor_t aDesc, - const void *A, - const void *alpha2, - const miopenTensorDescriptor_t bDesc, - const void *B, - const void *beta, - const miopenTensorDescriptor_t cDesc, - void *C) -// For Forward Bias use -// miopenConvolutionForwardBias. -``` -
- - -```c++ -cudnnStatus_t -cudnnOpTensor( - cudnnHandle_t handle, - const cudnnOpTensorDescriptor_t opTensorDesc, - const void *alpha1, - const cudnnTensorDescriptor_t aDesc, - const void *A, - const void *alpha2, - const cudnnTensorDescriptor_t bDesc, - const void *B, - const void *beta, - const cudnnTensorDescriptor_t cDesc, - void *C) -``` - - -```c++ -miopenStatus_t -miopenOpTensor( - miopenHandle_t handle, - miopenTensorOp_t tensorOp, - const void *alpha1, - const miopenTensorDescriptor_t aDesc, - const void *A, const void *alpha2, - const miopenTensorDescriptor_t bDesc, - const void *B, - const void *beta, - const miopenTensorDescriptor_t cDesc, - void *C) -``` -
- - -```c++ -cudnnStatus_t -cudnnSetTensor( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t yDesc, - void *y, - const void *valuePtr) -``` - - -```c++ -miopenStatus_t -miopenSetTensor( - miopenHandle_t handle, - const miopenTensorDescriptor_t yDesc, - void *y, - const void *alpha) -``` -
- - -```c++ -cudnnStatus_t -cudnnScaleTensor( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t yDesc, - void *y, - const void *alpha) -``` - - -```c++ -miopenStatus_t -miopenScaleTensor( - miopenHandle_t handle, - const miopenTensorDescriptor_t yDesc, - void *y, - const void *alpha) -``` -
-Filter - - -```c++ -cudnnStatus_t -cudnnCreateFilterDescriptor( - cudnnFilterDescriptor_t *filterDesc) -``` - - -```c++ -// All *FilterDescriptor* APIs are substituted by -// the respective TensorDescriptor APIs. -``` -
- - -```c++ -cudnnStatus_t -cudnnCreateConvolutionDescriptor( - cudnnConvolutionDescriptor_t *convDesc) -``` - - -```c++ -miopenStatus_t -miopenCreateConvolutionDescriptor( - miopenConvolutionDescriptor_t *convDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnSetConvolution2dDescriptor( - cudnnConvolutionDescriptor_t convDesc, - int pad_h, - int pad_w, - int u, - int v, - int upscalex, - int upscaley, - cudnnConvolutionMode_t mode) -``` - - -```c++ -miopenStatus_t -miopenInitConvolutionDescriptor( - miopenConvolutionDescriptor_t convDesc, - miopenConvolutionMode_t mode, - int pad_h, - int pad_w, - int u, - int v, - int upscalex, - int upscaley) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetConvolution2dDescriptor( - const cudnnConvolutionDescriptor_t convDesc, - int *pad_h, - int *pad_y, - int *u, - int *v, - int *upscalex, - int *upscaley, - cudnnConvolutionMode_t *mode) -``` - - -```c++ -miopenStatus_t -miopenGetConvolutionDescriptor( - miopenConvolutionDescriptor_t convDesc, - miopenConvolutionMode_t *mode, - int *pad_h, - int *pad_y, - int *u, - int *v, - int *upscalex, - int *upscaley) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetConvolution2dForwardOutputDim( - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t inputTensorDesc, - const cudnnFilterDescriptor_t filterDesc, - int *n, - int *c, - int *h, - int *w) -``` - - -```c++ -miopenStatus_t -miopenGetConvolutionForwardOutputDim( - miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t inputTensorDesc, - const miopenTensorDescriptor_t filterDesc, - int *n, - int *c, - int *h, - int *w) -``` -
- - -```c++ -cudnnStatus_t -cudnnDestroyConvolutionDescriptor( - cudnnConvolutionDescriptor_t convDesc) -``` - - -```c++ -miopenStatus_t -miopenDestroyConvolutionDescriptor( - miopenConvolutionDescriptor_t convDesc) -``` -
-Convolution - - -```c++ -cudnnStatus_t -cudnnFindConvolutionForwardAlgorithm( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const cudnnFilterDescriptor_t wDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t yDesc, - const int requestedAlgoCount, - int *returnedAlgoCount, - cudnnConvolutionFwdAlgoPerf_t *perfResults) - -``` -```c++ -cudnnStatus_t -cudnnFindConvolutionForwardAlgorithmEx( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnFilterDescriptor_t wDesc, - const void *w, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t yDesc, - void *y, - const int requestedAlgoCount, - int *returnedAlgoCount, - cudnnConvolutionFwdAlgoPerf_t *perfResults, - void *workSpace, - size_t workSpaceSizeInBytes) - -``` -```c++ -cudnnStatus_t -cudnnGetConvolutionForwardAlgorithm( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const cudnnFilterDescriptor_t wDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t yDesc, - cudnnConvolutionFwdPreference_t preference, - size_t memoryLimitInBytes, - cudnnConvolutionFwdAlgo_t *algo) -``` - - -```c++ -// FindConvolution() is mandatory. -// Allocate workspace prior to running this API. -// A table with times and memory requirements -// for different algorithms is returned. -// Users can choose the top-most algorithm if -// they only care about the fastest algorithm. -miopenStatus_t -miopenFindConvolutionForwardAlgorithm( - miopenHandle_t handle, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenTensorDescriptor_t wDesc, - const void *w, - const miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t yDesc, - void *y, - const int requestAlgoCount, - int *returnedAlgoCount, - miopenConvAlgoPerf_t *perfResults, - void *workSpace, - size_t workSpaceSize, - bool exhaustiveSearch) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetConvolutionForwardWorkspaceSize( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const cudnnFilterDescriptor_t wDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t yDesc, - cudnnConvolutionFwdAlgo_t algo, - size_t *sizeInBytes) -``` - - -```c++ -miopenStatus_t -miopenConvolutionForwardGetWorkSpaceSize( - miopenHandle_t handle, - const miopenTensorDescriptor_t wDesc, - const miopenTensorDescriptor_t xDesc, - const miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t yDesc, - size_t *workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnConvolutionForward( - cudnnHandle_t handle, - const void *alpha, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnFilterDescriptor_t wDesc, - const void *w, - const cudnnConvolutionDescriptor_t convDesc, - cudnnConvolutionFwdAlgo_t algo, - void *workSpace, - size_t workSpaceSizeInBytes, - const void *beta, - const cudnnTensorDescriptor_t yDesc, - void *y) -``` - - -```c++ -miopenStatus_t -miopenConvolutionForward( - miopenHandle_t handle, - const void *alpha, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenTensorDescriptor_t wDesc, - const void *w, - const miopenConvolutionDescriptor_t convDesc, - miopenConvFwdAlgorithm_t algo, - const void *beta, - const miopenTensorDescriptor_t yDesc, - void *y, - void *workSpace, - size_t workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnConvolutionBackwardBias( - cudnnHandle_t handle, - const void *alpha, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const void *beta, - const cudnnTensorDescriptor_t dbDesc, - void *db) -``` - - -```c++ -miopenStatus_t -miopenConvolutionBackwardBias( - miopenHandle_t handle, - const void *alpha, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const void *beta, - const miopenTensorDescriptor_t dbDesc, - void *db) -``` -
- - -```c++ -cudnnStatus_t -cudnnFindConvolutionBackwardFilterAlgorithm( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const cudnnTensorDescriptor_t dyDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnFilterDescriptor_t dwDesc, - const int requestedAlgoCount, - int *returnedAlgoCount, - cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) -``` -```c++ -cudnnStatus_t -cudnnFindConvolutionBackwardFilterAlgorithmEx( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnTensorDescriptor_t dyDesc, - const void *y, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnFilterDescriptor_t dwDesc, - void *dw, - const int requestedAlgoCount, - int *returnedAlgoCount, - cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, - void *workSpace, - size_t workSpaceSizeInBytes) - -``` -```c++ -cudnnStatus_t -cudnnGetConvolutionBackwardFilterAlgorithm( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const cudnnTensorDescriptor_t dyDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnFilterDescriptor_t dwDesc, - cudnnConvolutionBwdFilterPreference_t preference, - size_t memoryLimitInBytes, - cudnnConvolutionBwdFilterAlgo_t *algo) -``` - - -```c++ -// FindConvolution() is mandatory. -// Allocate workspace prior to running this API. -// A table with times and memory requirements -// for different algorithms is returned. -// Users can choose the top-most algorithm if -// they only care about the fastest algorithm. -miopenStatus_t -miopenFindConvolutionBackwardWeightsAlgorithm( - miopenHandle_t handle, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t dwDesc, - void *dw, - const int requestAlgoCount, - int *returnedAlgoCount, - miopenConvAlgoPerf_t *perfResults, - void *workSpace, - size_t workSpaceSize, - bool exhaustiveSearch) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetConvolutionBackwardFilterWorkspaceSize( - cudnnHandle_t handle, - const cudnnTensorDescriptor_t xDesc, - const cudnnTensorDescriptor_t dyDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnFilterDescriptor_t gradDesc, - cudnnConvolutionBwdFilterAlgo_t algo, - size_t *sizeInBytes) -``` - - -```c++ -miopenStatus_t -miopenConvolutionBackwardWeightsGetWorkSpaceSize( - miopenHandle_t handle, - const miopenTensorDescriptor_t dyDesc, - const miopenTensorDescriptor_t xDesc, - const miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t dwDesc, - size_t *workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnConvolutionBackwardFilter( - cudnnHandle_t handle, - const void *alpha, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnConvolutionDescriptor_t convDesc, - cudnnConvolutionBwdFilterAlgo_t algo, - void *workSpace, - size_t workSpaceSizeInBytes, - const void *beta, - const cudnnFilterDescriptor_t dwDesc, - void *dw) -``` - - -```c++ -miopenStatus_t -miopenConvolutionBackwardWeights( - miopenHandle_t handle, - const void *alpha, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenConvolutionDescriptor_t convDesc, - miopenConvBwdWeightsAlgorithm_t algo, - const void *beta, - const miopenTensorDescriptor_t dwDesc, - void *dw, - void *workSpace, - size_t workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetConvolutionBackwardDataWorkspaceSize( - cudnnHandle_t handle, - const cudnnFilterDescriptor_t wDesc, - const cudnnTensorDescriptor_t dyDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t dxDesc, - cudnnConvolutionBwdDataAlgo_t algo, - size_t *sizeInBytes) -``` - - -```c++ -miopenStatus_t -miopenConvolutionBackwardDataGetWorkSpaceSize( - miopenHandle_t handle, - const miopenTensorDescriptor_t dyDesc, - const miopenTensorDescriptor_t wDesc, - const miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t dxDesc, - size_t *workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnFindConvolutionBackwardDataAlgorithm( - cudnnHandle_t handle, - const cudnnFilterDescriptor_t wDesc, - const cudnnTensorDescriptor_t dyDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t dxDesc, - const int requestedAlgoCount, - int *returnedAlgoCount, - cudnnConvolutionBwdDataAlgoPerf_t *perfResults) - -``` -```c++ -cudnnStatus_t -cudnnFindConvolutionBackwardDataAlgorithmEx( - cudnnHandle_t handle, - const cudnnFilterDescriptor_t wDesc, - const void *w, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t dxDesc, - void *dx, - const int requestedAlgoCount, - int *returnedAlgoCount, - cudnnConvolutionBwdDataAlgoPerf_t *perfResults, - void *workSpace, - size_t workSpaceSizeInBytes) - -``` -```c++ -cudnnStatus_t -cudnnGetConvolutionBackwardDataAlgorithm( - cudnnHandle_t handle, - const cudnnFilterDescriptor_t wDesc, - const cudnnTensorDescriptor_t dyDesc, - const cudnnConvolutionDescriptor_t convDesc, - const cudnnTensorDescriptor_t dxDesc, - cudnnConvolutionBwdDataPreference_t preference, - size_t memoryLimitInBytes, - cudnnConvolutionBwdDataAlgo_t *algo) -``` - - -```c++ -// FindConvolution() is mandatory. -// Allocate workspace prior to running this API. -// A table with times and memory requirements -// for different algorithms is returned. -// Users can choose the top-most algorithm if -// they only care about the fastest algorithm. -miopenStatus_t -miopenFindConvolutionBackwardDataAlgorithm( - miopenHandle_t handle, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t wDesc, - const void *w, - const miopenConvolutionDescriptor_t convDesc, - const miopenTensorDescriptor_t dxDesc, - const void *dx, - const int requestAlgoCount, - int *returnedAlgoCount, - miopenConvAlgoPerf_t *perfResults, - void *workSpace, - size_t workSpaceSize, - bool exhaustiveSearch) -``` -
- - -```c++ -cudnnStatus_t -cudnnConvolutionBackwardData( - cudnnHandle_t handle, - const void *alpha, - const cudnnFilterDescriptor_t wDesc, - const void *w, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnConvolutionDescriptor_t convDesc, - cudnnConvolutionBwdDataAlgo_t algo, - void *workSpace, - size_t workSpaceSizeInBytes, - const void *beta, - const cudnnTensorDescriptor_t dxDesc, - void *dx) -``` - - -```c++ - miopenStatus_t - miopenConvolutionBackwardData( - miopenHandle_t handle, - const void *alpha, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t wDesc, - const void *w, - const miopenConvolutionDescriptor_t convDesc, - miopenConvBwdDataAlgorithm_t algo, - const void *beta, - const miopenTensorDescriptor_t dxDesc, - void *dx, - void *workSpace, - size_t workSpaceSize) -``` -
-Softmax - - -```c++ -cudnnStatus_t -cudnnSoftmaxForward( - cudnnHandle_t handle, - cudnnSoftmaxAlgorithm_t algo, - cudnnSoftmaxMode_t mode, - const void *alpha, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t yDesc, - void *y) -``` - - -```c++ -miopenStatus_t -miopenSoftmaxForward( - miopenHandle_t handle, - const void *alpha, - const miopenTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const miopenTensorDescriptor_t yDesc, - void *y) -``` -
- - -```c++ -cudnnStatus_t -cudnnSoftmaxBackward( - cudnnHandle_t handle, - cudnnSoftmaxAlgorithm_t algo, - cudnnSoftmaxMode_t mode, - const void *alpha, - const cudnnTensorDescriptor_t yDesc, - const void *y, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const void *beta, - const cudnnTensorDescriptor_t dxDesc, - void *dx) -``` - - -```c++ -miopenStatus_t -miopenSoftmaxBackward( - miopenHandle_t handle, - const void *alpha, - const miopenTensorDescriptor_t yDesc, - const void *y, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const void *beta, - const miopenTensorDescriptor_t dxDesc, - void *dx) -``` -
- - -```c++ -cudnnStatus_t -cudnnCreatePoolingDescriptor( - cudnnPoolingDescriptor_t *poolingDesc) - -``` - - -```c++ -miopenStatus_t -miopenCreatePoolingDescriptor( - miopenPoolingDescriptor_t *poolDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnSetPooling2dDescriptor( - cudnnPoolingDescriptor_t poolingDesc, - cudnnPoolingMode_t mode, - cudnnNanPropagation_t maxpoolingNanOpt, - int windowHeight, - int windowWidth, - int verticalPadding, - int horizontalPadding, - int verticalStride, - int horizontalStride) -``` - - -```c++ -miopenStatus_t -miopenSet2dPoolingDescriptor( - miopenPoolingDescriptor_t poolDesc, - miopenPoolingMode_t mode, - int windowHeight, - int windowWidth, - int pad_h, - int pad_w, - int u, - int v) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetPooling2dDescriptor( - const cudnnPoolingDescriptor_t poolingDesc, - cudnnPoolingMode_t *mode, - cudnnNanPropagation_t *maxpoolingNanOpt, - int *windowHeight, - int *windowWidth, - int *verticalPadding, - int *horizontalPadding, - int *verticalStride, - int *horizontalStride) -``` - - -```c++ -miopenStatus_t -miopenGet2dPoolingDescriptor( - const miopenPoolingDescriptor_t poolDesc, - miopenPoolingMode_t *mode, - int *windowHeight, - int *windowWidth, - int *pad_h, - int *pad_w, - int *u, - int *v) -``` -
-Pooling - - -```c++ -cudnnStatus_t -cudnnGetPooling2dForwardOutputDim( - const cudnnPoolingDescriptor_t poolingDesc, - const cudnnTensorDescriptor_t inputTensorDesc, - int *n, - int *c, - int *h, - int *w) -``` - - -```c++ -miopenStatus_t -miopenGetPoolingForwardOutputDim( - const miopenPoolingDescriptor_t poolDesc, - const miopenTensorDescriptor_t tensorDesc, - int *n, - int *c, - int *h, - int *w) -``` -
- - -```c++ -cudnnStatus_t -cudnnDestroyPoolingDescriptor( - cudnnPoolingDescriptor_t poolingDesc) -``` - - -```c++ -miopenStatus_t -miopenDestroyPoolingDescriptor( - miopenPoolingDescriptor_t poolDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnPoolingForward( - cudnnHandle_t handle, - const cudnnPoolingDescriptor_t poolingDesc, - const void *alpha, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t yDesc, - void *y) -``` - - -```c++ -miopenStatus_t -miopenPoolingForward( - miopenHandle_t handle, - const miopenPoolingDescriptor_t poolDesc, - const void *alpha, - const miopenTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const miopenTensorDescriptor_t yDesc, - void *y, - bool do_backward, - void *workSpace, - size_t workSpaceSize) -``` -
- - - - -```c++ -miopenStatus_t -miopenPoolingGetWorkSpaceSize( - const miopenTensorDescriptor_t yDesc, - size_t *workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnPoolingBackward( - cudnnHandle_t handle, - const cudnnPoolingDescriptor_t poolingDesc, - const void *alpha, - const cudnnTensorDescriptor_t yDesc, - const void *y, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t dxDesc, - void *dx) -``` - - -```c++ -miopenStatus_t -miopenPoolingBackward( - miopenHandle_t handle, - const miopenPoolingDescriptor_t poolDesc, - const void *alpha, - const miopenTensorDescriptor_t yDesc, - const void *y, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const miopenTensorDescriptor_t dxDesc, - void *dx, - const void *workspace) -``` -
- - -```c++ -cudnnStatus_t -cudnnCreateActivationDescriptor( - cudnnActivationDescriptor_t *activationDesc) -``` - - -```c++ -miopenStatus_t -miopenCreateActivationDescriptor( - miopenActivationDescriptor_t *activDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnSetActivationDescriptor( - cudnnActivationDescriptor_t activationDesc, - cudnnActivationMode_t mode, - cudnnNanPropagation_t reluNanOpt, - double reluCeiling) -``` - - -```c++ -miopenStatus_t -miopenSetActivationDescriptor( - const miopenActivationDescriptor_t activDesc, - miopenActivationMode_t mode, - double activAlpha, - double activBeta, - double activPower) -``` -
-Activation - - -```c++ -cudnnStatus_t -cudnnGetActivationDescriptor( - const cudnnActivationDescriptor_t activationDesc, - cudnnActivationMode_t *mode, - cudnnNanPropagation_t *reluNanOpt, - double *reluCeiling) -``` - - -```c++ -miopenStatus_t -miopenGetActivationDescriptor( - const miopenActivationDescriptor_t activDesc, - miopenActivationMode_t *mode, - double *activAlpha, - double *activBeta, - double *activPower) -``` -
- - -```c++ -cudnnStatus_t -cudnnDestroyActivationDescriptor( - cudnnActivationDescriptor_t activationDesc) -``` - - -```c++ -miopenStatus_t -miopenDestroyActivationDescriptor( - miopenActivationDescriptor_t activDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnActivationForward( - cudnnHandle_t handle, - cudnnActivationDescriptor_t activationDesc, - const void *alpha, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t yDesc, - void *y) -``` - - -```c++ -miopenStatus_t -miopenActivationForward( - miopenHandle_t handle, - const miopenActivationDescriptor_t activDesc, - const void *alpha, - const miopenTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const miopenTensorDescriptor_t yDesc, - void *y) -``` -
- - -```c++ -cudnnStatus_t -cudnnActivationBackward( - cudnnHandle_t handle, - cudnnActivationDescriptor_t activationDesc, - const void *alpha, - const cudnnTensorDescriptor_t yDesc, - const void *y, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t dxDesc, - void *dx) -``` - - -```c++ -miopenStatus_t -miopenActivationBackward( - miopenHandle_t handle, - const miopenActivationDescriptor_t activDesc, - const void *alpha, - const miopenTensorDescriptor_t yDesc, - const void *y, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const miopenTensorDescriptor_t dxDesc, - void *dx) -``` -
- - -```c++ -cudnnStatus_t -cudnnCreateLRNDescriptor( - cudnnLRNDescriptor_t *normDesc) -``` - - -```c++ -miopenStatus_t -miopenCreateLRNDescriptor( - miopenLRNDescriptor_t - *lrnDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnSetLRNDescriptor( - cudnnLRNDescriptor_t normDesc, - unsigned lrnN, - double lrnAlpha, - double lrnBeta, - double lrnK) -``` - - -```c++ -miopenStatus_t -miopenSetLRNDescriptor( - const miopenLRNDescriptor_t lrnDesc, - miopenLRNMode_t mode, - unsigned lrnN, - double lrnAlpha, - double lrnBeta, - double lrnK) -``` -
- - -```c++ -cudnnStatus_t -cudnnGetLRNDescriptor( - cudnnLRNDescriptor_t normDesc, - unsigned* lrnN, - double* lrnAlpha, - double* lrnBeta, - double* lrnK) -``` - - -```c++ -miopenStatus_t -miopenGetLRNDescriptor( - const miopenLRNDescriptor_t lrnDesc, - miopenLRNMode_t *mode, - unsigned *lrnN, - double *lrnAlpha, - double *lrnBeta, - double *lrnK) - -``` -
- LRN - - -```c++ -cudnnStatus_t -cudnnDestroyLRNDescriptor( - cudnnLRNDescriptor_t lrnDesc) -``` - - -```c++ -miopenStatus_t -miopenDestroyLRNDescriptor( - miopenLRNDescriptor_t lrnDesc) -``` -
- - -```c++ -cudnnStatus_t -cudnnLRNCrossChannelForward( - cudnnHandle_t handle, - cudnnLRNDescriptor_t normDesc, - cudnnLRNMode_t lrnMode, - const void* alpha, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t yDesc, - void *y) -``` - - -```c++ -miopenStatus_t -miopenLRNForward( - miopenHandle_t handle, - const miopenLRNDescriptor_t lrnDesc, - const void *alpha, - const miopenTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const miopenTensorDescriptor_t yDesc, - void *y, - bool do_backward, - void *workspace) -``` -
- - -```c++ -cudnnStatus_t -cudnnLRNCrossChannelBackward( - cudnnHandle_t handle, - cudnnLRNDescriptor_t normDesc, - cudnnLRNMode_t lrnMode, - const void* alpha, - const cudnnTensorDescriptor_t yDesc, - const void *y, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const void *beta, - const cudnnTensorDescriptor_t dxDesc, - void *dx) -``` - - -```c++ -miopenStatus_t -miopenLRNBackward( - miopenHandle_t handle, - const miopenLRNDescriptor_t lrnDesc, - const void *alpha, - const miopenTensorDescriptor_t yDesc, - const void *y, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t xDesc, - const void *x, const void *beta, - const miopenTensorDescriptor_t dxDesc, - void *dx, - const void *workspace) -``` -
- - - - - -```c++ -miopenStatus_t -miopenLRNGetWorkSpaceSize( - const miopenTensorDescriptor_t yDesc, - size_t *workSpaceSize) -``` -
- - -```c++ -cudnnStatus_t -cudnnDeriveBNTensorDescriptor( - cudnnTensorDescriptor_t derivedBnDesc, - const cudnnTensorDescriptor_t xDesc, - cudnnBatchNormMode_t mode) -``` - - -```c++ -miopenStatus_t -miopenDeriveBNTensorDescriptor( - miopenTensorDescriptor_t derivedBnDesc, - const miopenTensorDescriptor_t xDesc, - miopenBatchNormMode_t bn_mode) -``` -
- - -```c++ -cudnnStatus_t -cudnnBatchNormalizationForwardTraining( - cudnnHandle_t handle, - cudnnBatchNormMode_t mode, - void *alpha, - void *beta, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnTensorDescriptor_t yDesc, - void *y, - const cudnnTensorDescriptor_t - bnScaleBiasMeanVarDesc, - void *bnScale, - void *bnBias, - double exponentialAverageFactor, - void *resultRunningMean, - void *resultRunningVariance, - double epsilon, - void *resultSaveMean, - void *resultSaveInvVariance) -``` - - -```c++ -miopenStatus_t -miopenBatchNormalizationForwardTraining( - miopenHandle_t handle, - miopenBatchNormMode_t bn_mode, - void *alpha, - void *beta, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenTensorDescriptor_t yDesc, - void *y, - const miopenTensorDescriptor_t - bnScaleBiasMeanVarDesc, - void *bnScale, - void *bnBias, - double expAvgFactor, - void *resultRunningMean, - void *resultRunningVariance, - double epsilon, - void *resultSaveMean, - void *resultSaveInvVariance) -``` -
- Batch Normalization - - -```c++ -cudnnStatus_t -cudnnnBatchNormalizationForwardInference( - cudnnHandle_t handle, - cudnnBatchNormMode_t mode, - void *alpha, - void *beta, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnTensorDescriptor_t yDesc, - void *y, - const cudnnTensorDescriptor_t - bnScaleBiasMeanVarDesc, - const void *bnScale, - void *bnBias, - const void *estimatedMean, - const void *estimatedVariance, - double epsilon) -``` - - -```c++ -miopenStatus_t -miopenBatchNormalizationForwardInference( - miopenHandle_t handle, - miopenBatchNormMode_t bn_mode, - void *alpha, - void *beta, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenTensorDescriptor_t yDesc, - void *y, - const miopenTensorDescriptor_t - bnScaleBiasMeanVarDesc, - void *bnScale, - void *bnBias, - void *estimatedMean, - void *estimatedVariance, - double epsilon) -``` -
- - -```c++ -cudnnStatus_t -cudnnBatchNormalizationBackward( - cudnnHandle_t handle, - cudnnBatchNormMode_t mode, - const void *alphaDataDiff, - const void *betaDataDiff, - const void *alphaParamDiff, - const void *betaParamDiff, - const cudnnTensorDescriptor_t xDesc, - const void *x, - const cudnnTensorDescriptor_t dyDesc, - const void *dy, - const cudnnTensorDescriptor_t dxDesc, - void *dx, - const cudnnTensorDescriptor_t - bnScaleBiasDiffDesc, - const void *bnScale, - void *resultBnScaleDiff, - void *resultBnBiasDiff, - double epsilon, - const void *savedMean, - const void *savedInvVariance) -``` - - -```c++ -miopenStatus_t -miopenBatchNormalizationBackward( - miopenHandle_t handle, - miopenBatchNormMode_t bn_mode, - const void *alphaDataDiff, - const void *betaDataDiff, - const void *alphaParamDiff, - const void *betaParamDiff, - const miopenTensorDescriptor_t xDesc, - const void *x, - const miopenTensorDescriptor_t dyDesc, - const void *dy, - const miopenTensorDescriptor_t dxDesc, - void *dx, - const miopenTensorDescriptor_t - bnScaleBiasDiffDesc, - const void *bnScale, - void *resultBnScaleDiff, - void *resultBnBiasDiff, - double epsilon, - const void *savedMean, - const void *savedInvVariance) -``` -
- -

-
diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 56e5981142..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# How to build documentation via Sphinx - -```bash -pip3 install -r requirements.txt - -python -m sphinx -T -E -b html -d _build/doctrees -D language=en . _build/html -``` - diff --git a/docs/activation.rst b/docs/activation.rst deleted file mode 100644 index af0e27e7b6..0000000000 --- a/docs/activation.rst +++ /dev/null @@ -1,44 +0,0 @@ - - -Activation Layers -================= - -Activation types and layers. - -miopenActivationMode_t ----------------------- - -.. doxygenenum:: miopenActivationMode_t - -miopenCreateActivationDescriptor --------------------------------- - -.. doxygenfunction:: miopenCreateActivationDescriptor - - -miopenSetActivationDescriptor ------------------------------ - -.. doxygenfunction:: miopenSetActivationDescriptor - - -miopenGetActivationDescriptor ------------------------------ - -.. doxygenfunction:: miopenGetActivationDescriptor - -miopenActivationForward ------------------------ - -.. doxygenfunction:: miopenActivationForward - -miopenActivationBackward ------------------------- - -.. doxygenfunction:: miopenActivationBackward - -miopenDestroyActivationDescriptor ---------------------------------- - -.. doxygenfunction:: miopenDestroyActivationDescriptor - diff --git a/docs/apireference.rst b/docs/apireference.rst deleted file mode 100644 index 868bddf972..0000000000 --- a/docs/apireference.rst +++ /dev/null @@ -1,29 +0,0 @@ - -API Reference -============= - - -.. toctree:: - :maxdepth: 4 - :caption: Contents: - - datatypes - handle - tensor - activation - convolution - rnn - batchnorm - lrn - pooling - softmax - fusion - loss - dropout - reduction - layernorm - sum - argmax - groupnorm - cat - diff --git a/docs/argmax.rst b/docs/argmax.rst deleted file mode 100644 index f5a8cb3338..0000000000 --- a/docs/argmax.rst +++ /dev/null @@ -1,14 +0,0 @@ - -Argmax Layer(experimental) -======================== - -The argmax functions. -Find the index of the maximum value of a tensor across dimensions. -To enable this, define MIOPEN_BETA_API before including miopen.h. - - -miopenArgmaxForward ----------------------------------- - -.. doxygenfunction:: miopenArgmaxForward - diff --git a/docs/batchnorm.rst b/docs/batchnorm.rst deleted file mode 100644 index 05c03e3da7..0000000000 --- a/docs/batchnorm.rst +++ /dev/null @@ -1,30 +0,0 @@ - - -Batch Normalization Layer -========================= - - -miopenBatchNormMode_t ---------------------- - -.. doxygenenum:: miopenBatchNormMode_t - -miopenDeriveBNTensorDescriptor ------------------------------- - -.. doxygenfunction:: miopenDeriveBNTensorDescriptor - -miopenBatchNormalizationForwardTraining ---------------------------------------- - -.. doxygenfunction:: miopenBatchNormalizationForwardTraining - -miopenBatchNormalizationForwardInference ----------------------------------------- - -.. doxygenfunction:: miopenBatchNormalizationForwardInference - -miopenBatchNormalizationBackward --------------------------------- - -.. doxygenfunction:: miopenBatchNormalizationBackward diff --git a/docs/cache.md b/docs/cache.md deleted file mode 100644 index 7180cfc024..0000000000 --- a/docs/cache.md +++ /dev/null @@ -1,33 +0,0 @@ -Kernel Cache -============ - -MIOpen will cache binary kernels to disk, so they don't need to be compiled the next time the application is run. This cache is stored by default in `$HOME/.cache/miopen`. This location can be customized at build time by setting the `MIOPEN_CACHE_DIR` cmake variable. - -Clear the cache ---------------- - -The cache can be cleared by simply deleting the cache directory (i.e., `$HOME/.cache/miopen`). This should only be needed for development purposes or to free disk space. The cache does not need to be cleared when upgrading MIOpen. - -Disabling the cache -------------------- - -The are several ways to disable the cache. This is generally useful for development purposes. The cache can be disabled during build by either setting `MIOPEN_CACHE_DIR` to an empty string, or setting `BUILD_DEV=ON` when configuring cmake. The cache can also be disabled at runtime by setting the `MIOPEN_DISABLE_CACHE` environment variable to true. - -Updating MIOpen and removing the cache --------------------------------------- -For MIOpen version 2.3 and earlier, if the compiler changes, or the user modifies the kernels then the cache must be deleted for the MIOpen version in use; e.g., `rm -rf $HOME/.cache/miopen/`. More information about the cache can be found [here](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html). - -For MIOpen version 2.4 and later, MIOpen's kernel cache directory is versioned so that users' cached kernels will not collide when upgrading from earlier version. - -Installing pre-compiled kernels -------------------------------- -GPU architecture-specific pre-compiled kernel packages are available in the ROCm package repositories, to reduce the startup latency of MIOpen kernels. In essence, these packages have the kernel cache file mentioned above and install them in the ROCm installation directory along with other MIOpen artifacts. Thus, when launching a kernel, MIOpen will first check for the existence of a kernel in the kernel cache installed in the MIOpen installation directory. If the file does not exist or the required kernel is not found, the kernel is compiled and placed in the user's kernel cache. - -These packages are optional for the functioning of MIOpen and must be separately installed from MIOpen. Users who wish to conserve disk space may choose not to install these packages at the cost of higher startup latency. Users have the flexibility to only install kernel packages for installed device architecture, thus minimizing disk space usage. - -If MIOpen kernels package is not installed, or if we do not deliver the kernels suitable for the user's GPU, then the user will get warning message like this: -> MIOpen(HIP): Warning [SQLiteBase] Missing system database file:gfx906_60.kdb Performance may degrade - -The performance degradation mentioned in the warning only affects the network start-up time (aka "initial iteration time") and thus can be safely ignored. - -Please refer to the MIOpen installation instructions: [installing MIOpen kernels package](https://rocm.docs.amd.com/projects/MIOpen/en/latest/install.html#installing-miopen-kernels-package) for guidance on installing the MIOpen kernels package. diff --git a/docs/cat.rst b/docs/cat.rst deleted file mode 100644 index 46da565753..0000000000 --- a/docs/cat.rst +++ /dev/null @@ -1,14 +0,0 @@ - -Cat Layer(experimental) -=========================== - -The cat types and functions. -Concatenates the given sequence of seq tensors in the given dimension. -To enable this, define MIOPEN_BETA_API before including miopen.h. - - -miopenCatForward ------------------------ - -.. doxygenfunction:: miopenCatForward - diff --git a/docs/conceptual/MI200-alt-implementation.rst b/docs/conceptual/MI200-alt-implementation.rst new file mode 100644 index 0000000000..b01dc5542c --- /dev/null +++ b/docs/conceptual/MI200-alt-implementation.rst @@ -0,0 +1,30 @@ +.. meta:: + :description: MI200 MFMA behavior specifics + :keywords: MIOpen, ROCm, API, documentation, MI200, MFMA + +*********************************************************************************** +MI200 matrix fused multiply-add (MFMA) behavior specifics +*********************************************************************************** + +The MI200 ``MFMA_F16``, ``MFMA_BF16``, and ``MFMA_BF16_1K`` flush subnormal input/output data to +zero. This behavior might affect the convolution operation in certain workloads due to the limited +exponent range of the half-precision floating-point datatypes. + +MIOpen offers an alternate implementation for the half-precision datatype via conversion instructions +to utilize the BFloat16 datatype's larger exponent range, albeit with reduced accuracy. The following +salients apply to this alternate implementation: + +* It's disabled by default in the forward convolution operations. + +* It's enabled by default in the backward data and backward weights convolution operations. + +* You can override the default MIOpen behaviors by using the ``miopenSetConvolutionAttribute`` API + call: Pass the convolution descriptor for the appropriate convolution operation, and the + ``MIOPEN_CONVOLUTION_ATTRIB_FP16_ALT_IMPL`` convolution attribute (with a non-zero value), to + engage the alternate implementation. + +* You can also override the behavior using the + ``MIOPEN_DEBUG_CONVOLUTION_ATTRIB_FP16_ALT_IMPL`` environment variable. When set to ``1``, + ``MIOPEN_DEBUG_CONVOLUTION_ATTRIB_FP16_ALT_IMPL`` engages the alternate implementation; + when set to ``0``, it's disabled. Keep in mind that the environment variable impacts the convolution + operation in all directions. diff --git a/docs/conceptual/cache.rst b/docs/conceptual/cache.rst new file mode 100644 index 0000000000..af8fb1a2ba --- /dev/null +++ b/docs/conceptual/cache.rst @@ -0,0 +1,65 @@ +.. meta:: + :description: Using kernel cache + :keywords: MIOpen, ROCm, API, documentation, kernel cache + +******************************************************************** +Kernel cache +******************************************************************** + +MIOpen caches binary kernels to disk so they don't need to be compiled the next time you run the +application. This cache is stored in ``$HOME/.cache/miopen`` by default, but you can change this at +build time by setting the ``MIOPEN_CACHE_DIR`` CMake variable. + +Clear the cache +==================================================== + +You can clear the cache by deleting the cache directory (e.g., ``$HOME/.cache/miopen``). We +recommend that you only do this for development purposes or to free disk space. You don't need to +clear the cache when upgrading MIOpen. + +Disabling the cache +==================================================== + +Disabling the cache is generally useful for development purposes. You can disable the cache: + +* During **build**, either by setting ``MIOPEN_CACHE_DIR`` to an empty string or setting + ``BUILD_DEV=ON`` when configuring CMake +* At **runtime** by setting the ``MIOPEN_DISABLE_CACHE`` environment variable to ``true``. + +Updating MIOpen and removing the cache +=============================================================== + +For MIOpen version 2.3 and earlier, if the compiler changes or you modify the kernels. then you must +delete the cache for the existing MIOpen version +(e.g., ``rm -rf $HOME/.cache/miopen/``). + +For MIOpen version 2.4 and later, MIOpen's kernel cache directory is versioned, so cached kernels +won't collide when upgrading. + +Installing pre-compiled kernels +==================================================== + +GPU architecture-specific, pre-compiled kernel packages are available in the ROCm package +repositories. These reduce the startup latency of MIOpen kernels (they contain the kernel cache file +and install it in the ROCm installation directory along with other MIOpen artifacts). When launching a +kernel, MIOpen first checks for a kernel in the kernel cache within the MIOpen installation directory. If +the file doesn't exist, or the required kernel isn't found, the kernel is compiled and placed in your +kernel cache. + +These packages are optional and must be separately installed from MIOpen. If you want to conserve +disk space, you may choose not to install these packages (though without them, you'll have higher +startup latency). You also have the option to only install kernel packages for your device architecture, +which helps save disk space. + +If the MIOpen kernels package is not installed, or if the kernel doens't match the GPU, you'll get a +warning message similar to: + +.. code:: bash + + > MIOpen(HIP): Warning [SQLiteBase] Missing system database file:gfx906_60.kdb Performance may degrade + +The performance degradation mentioned in the warning only affects the network start-up time (the +"initial iteration time") and can be safely ignored. + +Refer to the :doc:`installation instructions <../install/install>` for guidance on installing the MIOpen +kernels package. diff --git a/docs/conceptual/finddb.rst b/docs/conceptual/finddb.rst new file mode 100644 index 0000000000..09cefa6c8b --- /dev/null +++ b/docs/conceptual/finddb.rst @@ -0,0 +1,75 @@ +.. meta:: + :description: Using the Find Database + :keywords: MIOpen, ROCm, API, documentation + +******************************************************************** +Using the find database +******************************************************************** + +Prior to MIOpen 2.0, you could use calls (such as ``miopenFindConvolution*Algorithm()``) to gather a +set of convolution algorithms in the form of an array of ``miopenConvSolution_t`` structs. This process +is time-consuming because it requires online benchmarking of competing algorithms. + +As of MIOpen 2.0, we introduced an :doc:`immediate mode <../how-to/find-and-immediate>`, which +is based on a database that contains the results of calls to the legacy ``Find()`` stage. We refer to the +find database as FindDb. + +FindDb consists of two parts: + +* **System FindDb**: A system-wide storage that holds pre-run values for the most applicable + configurations. +* **User FindDb**: A per-user storage that holds results for arbitrary user-run configurations. It also + serves as a cache for the ``Find()`` stage. + +User FindDb `always takes precedence` over System FindDb. + +By default, System FindDb resides within MIOpen's install location, while User FindDb resides in your +home directory. + +Note that: + +* The System FindDb is `not` modified upon installation of MIOpen. +* There are separate Find databases for HIP and OpenCL backends. + +Populating User FindDb +============================================================= + +MIOpen collects FindDb information during the following API calls: + +* ``miopenFindConvolutionForwardAlgorithm()`` +* ``miopenFindConvolutionBackwardDataAlgorithm()`` +* ``miopenFindConvolutionBackwardWeightsAlgorithm()`` + +During the call, find data entries are collected for one `problem configuration`, which is implicitly +defined by the tensor descriptors and convolution descriptor passed to API function. + + +Updating MIOpen and User FindDb +============================================================= + +When you install a new version of MIOpen, this new version ignores old User FindDb files. Therefore, +you don't need to move or delete the old User FindDb files. + +If you want to re-collect the information into the new User FindDb, you can use the same steps you +followed in the previous version. Re-collecting information keeps immediate mode optimized. + + +Disabling FindDb +============================================================= + +You can disable FindDb by setting the ``MIOPEN_DEBUG_DISABLE_FIND_DB`` environmental variable +to 1: + +.. code:: bash + + export MIOPEN_DEBUG_DISABLE_FIND_DB=1 + + +.. note:: + + System FindDb can be cached into memory and may dramatically increase performance. To disable + this option, set the ``DMIOPEN_DEBUG_FIND_DB_CACHING`` CMake configuration flag to off. + +.. code:: bash + + -DMIOPEN_DEBUG_FIND_DB_CACHING=Off diff --git a/docs/conceptual/perfdb.rst b/docs/conceptual/perfdb.rst new file mode 100644 index 0000000000..96ce0d74ff --- /dev/null +++ b/docs/conceptual/perfdb.rst @@ -0,0 +1,85 @@ +.. meta:: + :description: Using the performance database + :keywords: MIOpen, ROCm, API, documentation, performance database + +************************************************************************************************ +Using the performance database +************************************************************************************************ + +Many MIOpen kernels have parameters that affect their performance. Setting these parameters to +optimal values allows for the best possible throughput. Optimal values depend on many factors, +including network configuration, GPU type, clock frequencies, and ROCm version. + +Due to the large number of possible configurations and settings, MIOpen provides a set of pre-tuned +values for the `most applicable` network configurations and a means for expanding the set of +optimized values. MIOpen's performance database (PerfDb) contains these pre-tuned parameter values +in addition to any user-optimized parameters. + +The PerfDb consists of two parts: + +* **System PerfDb**: A system-wide storage that holds pre-run values for the most applicable + configurations. +* **User PerfDb**: A per-user storage that holds optimized values for arbitrary configurations. + +User PerfDb `always takes precedence` over System PerfDb. + +MIOpen also has auto-tuning functionality, which is able to find optimized kernel parameter values for +a specific configuration. The auto-tune process may take a long time, but once optimized values are +found, they're stored in the User PerfDb. MIOpen then automatically reads and uses these parameter +values. + +By default, System PerfDb resides within MIOpen's install location, while User PerfDb resides in your +home directory. See :ref:` setting up locations ` for more information. + +System PerfDb is not modified during MIOpen installation. + +Auto-tuning kernels +========================================================== + +MIOpen performs auto-tuning during the these API calls: + +* ``miopenFindConvolutionForwardAlgorithm()`` +* ``miopenFindConvolutionBackwardDataAlgorithm()`` +* ``miopenFindConvolutionBackwardWeightsAlgorithm()`` + +Auto-tuning is performed for only one `problem configuration`, which is implicitly defined by the +tensor descriptors that are passed to the API function. + +In order for auto-tuning to begin, the following conditions must be met: + +* The applicable kernels have tuning parameters +* The value of the ``exhaustiveSearch`` parameter is ``true`` +* Neither System nor User PerfDb can contain values for the relevant `problem configuration`. + +You can override the latter two conditions by enforcing the search using the +``- MIOPEN_FIND_ENFORCE`` environment variable. You can also use this variable to remove values +from User PerfDb, as described in the following section. + +Using MIOPEN_FIND_ENFORCE +---------------------------------------------------------------------------------------------------------- + +``MIOPEN_FIND_ENFORCE`` supports symbolic (case-insensitive) and numeric values. Possible values +are: + +* ``NONE``/``(1)``: No change in the default behavior. +* ``DB_UPDATE/``(2)``: Do not skip auto-tune (even if PerfDb already contains optimized values). If you + request auto-tune via API, MIOpen performs it and updates PerfDb. You can use this mode for + fine-tuning the MIOpen installation on your system. However, this mode slows down processes. +* ``SEARCH``/``(3)``: Perform auto-tune even if not requested via API. In this case, the library behaves as + if the ``exhaustiveSearch`` parameter set to ``true``. If PerfDb already contains optimized values, + auto-tune is not performed. You can use this mode to tune applications that don't anticipate means + for getting the best performance from MIOpen. When in this mode, your application's first run may + take substantially longer than expected. +* ``SEARCH_DB_UPDATE``/``(4)``: A combination of ``DB_UPDATE`` and ``SEARCH``. MIOpen performs + auto-tune (and updates User PerfDb) on each ``miopenFindConvolution*()`` call. This mode is + recommended only for debugging purposes. +* ``DB_CLEAN``/``(5)``: Removes optimized values related to the `problem configuration` from User + PerfDb. Auto-tune is blocked, even if explicitly requested. System PerfDb is left intact. **Use this + option with care.** + +Updating MIOpen and User PerfDb +========================================================== + +If you install a new version of MIOpen, we strongly recommend moving or deleting your old User +PerfDb file. This prevents older database entries from affecting configurations within the newer system +database. The User PerfDb is named ``miopen.udb`` and is located at the User PerfDb path. diff --git a/docs/conceptual/porting-guide.rst b/docs/conceptual/porting-guide.rst new file mode 100644 index 0000000000..928ef14cfa --- /dev/null +++ b/docs/conceptual/porting-guide.rst @@ -0,0 +1,1528 @@ +.. meta:: + :description: Porting MIOpen + :keywords: MIOpen, ROCm, API, documentation, porting + +******************************************************************** +Porting to MIOpen +******************************************************************** + +The following is a summary of the key differences between MIOpen and cuDNN. + +* Calling ``miopenFindConvolution*Algorithm()`` is `mandatory` before calling any Convolution API +* The typical calling sequence for MIOpen Convolution APIs is: + + * ``miopenConvolution*GetWorkSpaceSize()`` (returns the workspace size required by ``Find()``) + * ``miopenFindConvolution*Algorithm()`` (returns performance information for various algorithms) + * ``miopenConvolution*()`` + +MIOpen supports: + +* 4D tensors in the NCHW and NHWC storage format; the cuDNN ``__“\*Nd\*”__`` APIs don't have a + corresponding MIOpen API +* ``__`float(fp32)`__`` datatype +* ``__2D Convolutions__`` and ``__3D Convolutions__`` +* ``__2D Pooling__`` + +MIOpen doesn't support: + +* ``__Preferences__`` for convolutions +* Softmax modes (MIOpen implements the ``__SOFTMAX_MODE_CHANNEL__`` flavor) +* ``__Transform-Tensor__``, ``__Dropout__``, ``__RNNs__``, and ``__Divisive Normalization__`` + +Useful MIOpen environment variables include: + +* ``MIOPEN_ENABLE_LOGGING=1``: Logs all the MIOpen APIs called, including the parameters passed + to those APIs +* ``MIOPEN_DEBUG_AMD_ROCM_PRECOMPILED_BINARIES=0``: Disables the Winograd convolution + algorithm +* ``MIOPEN_DEBUG_GCN_ASM_KERNELS=0``: Disables hand-tuned ASM kernels for the direct + convolution algorithm (the fall-back is to kernels written in high-level language) +* ``MIOPEN_DEBUG_CONV_FFT=0``: Disables the FFT convolution algorithm +* ``MIOPEN_DEBUG_CONV_DIRECT=0``: Disables the direct convolution algorithm + +cuDNN versus MIOpen APIs +=================================================== + +The following sections compare cuDNN and MIOpen APIs with similar functions. + +Handle operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreate( + cudnnHandle_t *handle) + - .. code-block:: cpp + + miopenStatus_t + miopenCreate( + miopenHandle_t *handle) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDestroy( + cudnnHandle_t handle) + - .. code-block:: cpp + + miopenStatus_t + miopenDestroy( + miopenHandle_t handle) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSetStream( + cudnnHandle_t handle, + cudaStream_t streamId) + - .. code-block:: cpp + + miopenStatus_t + miopenSetStream( + miopenHandle_t handle, + miopenAcceleratorQueue_t streamID) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetStream( + cudnnHandle_t handle, + cudaStream_t *streamId) + - .. code-block:: cpp + + miopenStatus_t + miopenGetStream( + miopenHandle_t handle, + miopenAcceleratorQueue_t *streamID) + +Tensor operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreateTensorDescriptor( + cudnnTensorDescriptor_t *tensorDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenCreateTensorDescriptor( + miopenTensorDescriptor_t + *tensorDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDestroyTensorDescriptor( + cudnnTensorDescriptor_t tensorDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenDestroyTensorDescriptor( + miopenTensorDescriptor_t tensorDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSetTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *valuePtr) + - .. code-block:: cpp + + miopenStatus_t + miopenSetTensor( + miopenHandle_t handle, + const miopenTensorDescriptor_t yDesc, + void *y, + const void *alpha) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSetTensor4dDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnTensorFormat_t format, + cudnnDataType_t dataType, + int n, + int c, + int h, + int w) + - .. code-block:: cpp + + miopenStatus_t miopenSet4dTensorDescriptor( + miopenTensorDescriptor_t tensorDesc, + miopenDataType_t dataType, + int n, + int c, + int h, + int w) + + (Only ``NCHW`` format is supported) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetTensor4dDescriptor( + cudnnTensorDescriptor_t tensorDesc, + cudnnDataType_t *dataType, + int *n, + int *c, + int *h, + int *w, + int *nStride, + int *cStride, + int *hStride, + int *wStride) + - .. code-block:: cpp + + miopenStatus_t + miopenGet4dTensorDescriptor( + miopenTensorDescriptor_t tensorDesc, + miopenDataType_t *dataType, + int *n, + int *c, + int *h, + int *w, + int *nStride, + int *cStride, + int *hStride, + int *wStride) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnAddTensor( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) + - .. code-block:: cpp + + miopenStatus_t + miopenOpTensor( + miopenHandle_t handle, + miopenTensorOp_t tensorOp, + const void *alpha1, + constmiopenTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const miopenTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const miopenTensorDescriptor_t cDesc, + void *C) + + For forward bias, use ``miopenConvolutionForwardBias``. + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnOpTensor( + cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) + - .. code-block:: cpp + + miopenStatus_t + miopenOpTensor( + miopenHandle_t handle, + miopenTensorOp_t tensorOp, + const void *alpha1, + const miopenTensorDescriptor_t aDesc, + const void *A, const void *alpha2, + const miopenTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const miopenTensorDescriptor_t cDesc, + void *C) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnOpTensor( + cudnnHandle_t handle, + const cudnnOpTensorDescriptor_t opTensorDesc, + const void *alpha1, + const cudnnTensorDescriptor_t aDesc, + const void *A, + const void *alpha2, + const cudnnTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const cudnnTensorDescriptor_t cDesc, + void *C) + - .. code-block:: cpp + + miopenStatus_t + miopenOpTensor( + miopenHandle_t handle, + miopenTensorOp_t tensorOp, + const void *alpha1, + const miopenTensorDescriptor_t aDesc, + const void *A, const void *alpha2, + const miopenTensorDescriptor_t bDesc, + const void *B, + const void *beta, + const miopenTensorDescriptor_t cDesc, + void *C) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnScaleTensor( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t yDesc, + void *y, + const void *alpha) + - .. code-block:: cpp + + miopenStatus_t + miopenScaleTensor( + miopenHandle_t handle, + const miopenTensorDescriptor_t yDesc, + void *y, + const void *alpha) + +Filter operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreateFilterDescriptor( + cudnnFilterDescriptor_t *filterDesc) + - All ``FilterDescriptor`` APIs are substituted by their respective ``TensorDescriptor`` API. + +Convolution operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreateConvolutionDescriptor( + cudnnConvolutionDescriptor_t *convDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenCreateConvolutionDescriptor( + miopenConvolutionDescriptor_t *convDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDestroyConvolutionDescriptor( + cudnnConvolutionDescriptor_t convDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenDestroyConvolutionDescriptor( + miopenConvolutionDescriptor_t convDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolution2dDescriptor( + const cudnnConvolutionDescriptor_t convDesc, + int *pad_h, + int *pad_y, + int *u, + int *v, + int *upscalex, + int *upscaley, + cudnnConvolutionMode_t *mode) + - .. code-block:: cpp + + miopenStatus_t + miopenGetConvolutionDescriptor( + miopenConvolutionDescriptor_t convDesc, + miopenConvolutionMode_t *mode, + int *pad_h, + int *pad_y, + int *u, + int *v, + int *upscalex, + int *upscaley) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolution2dForwardOutputDim( + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + const cudnnFilterDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w) + - .. code-block:: cpp + + miopenStatus_t + miopenGetConvolutionForwardOutputDim( + miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t inputTensorDesc, + const miopenTensorDescriptor_t filterDesc, + int *n, + int *c, + int *h, + int *w) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolutionForwardWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdAlgo_t algo, + size_t *sizeInBytes) + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionForwardGetWorkSpaceSize( + miopenHandle_t handle, + const miopenTensorDescriptor_t wDesc, + const miopenTensorDescriptor_t xDesc, + const miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t yDesc, + size_t *workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolutionBackwardFilterWorkspaceSize( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t gradDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + size_t *sizeInBytes) + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionBackwardWeightsGetWorkSpaceSize( + miopenHandle_t handle, + const miopenTensorDescriptor_t dyDesc, + const miopenTensorDescriptor_t xDesc, + const miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t dwDesc, + size_t *workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolutionBackwardDataWorkspaceSize( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataAlgo_t algo, + size_t *sizeInBytes + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionBackwardDataGetWorkSpaceSize( + miopenHandle_t handle, + const miopenTensorDescriptor_t dyDesc, + const miopenTensorDescriptor_t wDesc, + const miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t dxDesc, + size_t *workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnConvolutionForward( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionFwdAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionForward( + miopenHandle_t handle, + const void *alpha, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenTensorDescriptor_t wDesc, + const void *w, + const miopenConvolutionDescriptor_t convDesc, + miopenConvFwdAlgorithm_t algo, + const void *beta, + const miopenTensorDescriptor_t yDesc, + void *y, + void *workSpace, + size_t workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnFindConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults) + + .. code-block:: cpp + + cudnnStatus_t + cudnnFindConvolutionForwardAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + void *y, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionFwdAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) + + .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolutionForwardAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnFilterDescriptor_t wDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t yDesc, + cudnnConvolutionFwdPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionFwdAlgo_t *algo) + + - ``FindConvolution()`` is mandatory. + Allocate workspace prior to running this API. + A table with times and memory requirements for different algorithms is returned. + You can choose the top-most algorithm if you want only the fastest algorithm. + + .. code-block:: cpp + + miopenStatus_t + miopenFindConvolutionForwardAlgorithm( + miopenHandle_t handle, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenTensorDescriptor_t wDesc, + const void *w, + const miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t yDesc, + void *y, + const int requestAlgoCount, + int *returnedAlgoCount, + miopenConvAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSize, + bool exhaustiveSearch) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnConvolutionBackwardBias( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dbDesc, + void *db) + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionBackwardBias( + miopenHandle_t handle, + const void *alpha, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const miopenTensorDescriptor_t dbDesc, + void *db) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnFindConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults) + + .. code-block:: cpp + + cudnnStatus_t + cudnnFindConvolutionBackwardFilterAlgorithmEx( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *y, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + void *dw, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdFilterAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) + + .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolutionBackwardFilterAlgorithm( + cudnnHandle_t handle, + const cudnnTensorDescriptor_t xDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnFilterDescriptor_t dwDesc, + cudnnConvolutionBwdFilterPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdFilterAlgo_t *algo) + + - ``FindConvolution()`` is mandatory. + Allocate workspace prior to running this API. + A table with times and memory requirements for different algorithms is returned. + You can choose the top-most algorithm if you want only the fastest algorithm. + + .. code-block:: cpp + + miopenStatus_t + miopenFindConvolutionBackwardWeightsAlgorithm( + miopenHandle_t handle, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t dwDesc, + void *dw, + const int requestAlgoCount, + int *returnedAlgoCount, + miopenConvAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSize, + bool exhaustiveSearch) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnFindConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults) + + .. code-block:: cpp + + cudnnStatus_t + cudnnFindConvolutionBackwardDataAlgorithmEx( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const int requestedAlgoCount, + int *returnedAlgoCount, + cudnnConvolutionBwdDataAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSizeInBytes) + + .. code-block:: cpp + + cudnnStatus_t + cudnnGetConvolutionBackwardDataAlgorithm( + cudnnHandle_t handle, + const cudnnFilterDescriptor_t wDesc, + const cudnnTensorDescriptor_t dyDesc, + const cudnnConvolutionDescriptor_t convDesc, + const cudnnTensorDescriptor_t dxDesc, + cudnnConvolutionBwdDataPreference_t preference, + size_t memoryLimitInBytes, + cudnnConvolutionBwdDataAlgo_t *algo) + + - ``FindConvolution()`` is mandatory. + Allocate workspace prior to running this API. + A table with times and memory requirements for different algorithms is returned. + You can choose the top-most algorithm if you want only the fastest algorithm. + + .. code-block:: cpp + + miopenStatus_t + miopenFindConvolutionBackwardDataAlgorithm( + miopenHandle_t handle, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t wDesc, + const void *w, + const miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t dxDesc, + const void *dx, + const int requestAlgoCount, + int *returnedAlgoCount, + miopenConvAlgoPerf_t *perfResults, + void *workSpace, + size_t workSpaceSize, + bool exhaustiveSearch) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnConvolutionBackwardFilter( + cudnnHandle_t handle, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdFilterAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnFilterDescriptor_t dwDesc, + void *dw) + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionBackwardWeights( + miopenHandle_t handle, + const void *alpha, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenConvolutionDescriptor_t convDesc, + miopenConvBwdWeightsAlgorithm_t algo, + const void *beta, + const miopenTensorDescriptor_t dwDesc, + void *dw, + void *workSpace, + size_t workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnConvolutionBackwardData( + cudnnHandle_t handle, + const void *alpha, + const cudnnFilterDescriptor_t wDesc, + const void *w, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnConvolutionDescriptor_t convDesc, + cudnnConvolutionBwdDataAlgo_t algo, + void *workSpace, + size_t workSpaceSizeInBytes, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) + - .. code-block:: cpp + + miopenStatus_t + miopenConvolutionBackwardData( + miopenHandle_t handle, + const void *alpha, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t wDesc, + const void *w, + const miopenConvolutionDescriptor_t convDesc, + miopenConvBwdDataAlgorithm_t algo, + const void *beta, + const miopenTensorDescriptor_t dxDesc, + void *dx, + void *workSpace, + size_t workSpaceSize) + +Softmax operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSoftmaxForward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) + - .. code-block:: cpp + + miopenStatus_t + miopenSoftmaxForward( + miopenHandle_t handle, + const void *alpha, + const miopenTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const miopenTensorDescriptor_t yDesc, + void *y) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSoftmaxBackward( + cudnnHandle_t handle, + cudnnSoftmaxAlgorithm_t algo, + cudnnSoftmaxMode_t mode, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) + - .. code-block:: cpp + + miopenStatus_t + miopenSoftmaxBackward( + miopenHandle_t handle, + const void *alpha, + const miopenTensorDescriptor_t yDesc, + const void *y, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const void *beta, + const miopenTensorDescriptor_t dxDesc, + void *dx) + +Pooling operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreatePoolingDescriptor( + cudnnPoolingDescriptor_t *poolingDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenCreatePoolingDescriptor( + miopenPoolingDescriptor_t *poolDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSetPooling2dDescriptor( + cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t mode, + cudnnNanPropagation_t maxpoolingNanOpt, + int windowHeight, + int windowWidth, + int verticalPadding, + int horizontalPadding, + int verticalStride, + int horizontalStride) + - .. code-block:: cpp + + miopenStatus_t + miopenSet2dPoolingDescriptor( + miopenPoolingDescriptor_t poolDesc, + miopenPoolingMode_t mode, + int windowHeight, + int windowWidth, + int pad_h, + int pad_w, + int u, + int v) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetPooling2dDescriptor( + const cudnnPoolingDescriptor_t poolingDesc, + cudnnPoolingMode_t *mode, + cudnnNanPropagation_t *maxpoolingNanOpt, + int *windowHeight, + int *windowWidth, + int *verticalPadding, + int *horizontalPadding, + int *verticalStride, + int *horizontalStride) + - .. code-block:: cpp + + miopenStatus_t + miopenGet2dPoolingDescriptor( + const miopenPoolingDescriptor_t poolDesc, + miopenPoolingMode_t *mode, + int *windowHeight, + int *windowWidth, + int *pad_h, + int *pad_w, + int *u, + int *v) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetPooling2dForwardOutputDim( + const cudnnPoolingDescriptor_t poolingDesc, + const cudnnTensorDescriptor_t inputTensorDesc, + int *n, + int *c, + int *h, + int *w) + - .. code-block:: cpp + + miopenStatus_t + miopenGetPoolingForwardOutputDim( + const miopenPoolingDescriptor_t poolDesc, + const miopenTensorDescriptor_t tensorDesc, + int *n, + int *c, + int *h, + int *w) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDestroyPoolingDescriptor( + cudnnPoolingDescriptor_t poolingDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenDestroyPoolingDescriptor( + miopenPoolingDescriptor_t poolDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnPoolingForward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) + - .. code-block:: cpp + + miopenStatus_t + miopenPoolingForward( + miopenHandle_t handle, + const miopenPoolingDescriptor_t poolDesc, + const void *alpha, + const miopenTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const miopenTensorDescriptor_t yDesc, + void *y, + bool do_backward, + void *workSpace, + size_t workSpaceSize) + + * + - NA + - .. code-block:: cpp + + miopenStatus_t + miopenPoolingGetWorkSpaceSize( + const miopenTensorDescriptor_t yDesc, + size_t *workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnPoolingBackward( + cudnnHandle_t handle, + const cudnnPoolingDescriptor_t poolingDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) + - .. code-block:: cpp + + miopenStatus_t + miopenPoolingBackward( + miopenHandle_t handle, + const miopenPoolingDescriptor_t poolDesc, + const void *alpha, + const miopenTensorDescriptor_t yDesc, + const void *y, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const miopenTensorDescriptor_t dxDesc, + void *dx, + const void *workspace) + +Activation operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreateActivationDescriptor( + cudnnActivationDescriptor_t *activationDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenCreateActivationDescriptor( + miopenActivationDescriptor_t *activDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSetActivationDescriptor( + cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t mode, + cudnnNanPropagation_t reluNanOpt, + double reluCeiling) + - .. code-block:: cpp + + miopenStatus_t + miopenSetActivationDescriptor( + const miopenActivationDescriptor_t activDesc, + miopenActivationMode_t mode, + double activAlpha, + double activBeta, + double activPower) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetActivationDescriptor( + const cudnnActivationDescriptor_t activationDesc, + cudnnActivationMode_t *mode, + cudnnNanPropagation_t *reluNanOpt, + double *reluCeiling) + - .. code-block:: cpp + + miopenStatus_t + miopenGetActivationDescriptor( + const miopenActivationDescriptor_t activDesc, + miopenActivationMode_t *mode, + double *activAlpha, + double *activBeta, + double *activPower) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDestroyActivationDescriptor( + cudnnActivationDescriptor_t activationDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenDestroyActivationDescriptor( + miopenActivationDescriptor_t activDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnActivationForward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) + - .. code-block:: cpp + + miopenStatus_t + miopenActivationForward( + miopenHandle_t handle, + const miopenActivationDescriptor_t activDesc, + const void *alpha, + const miopenTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const miopenTensorDescriptor_t yDesc, + void *y) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnActivationBackward( + cudnnHandle_t handle, + cudnnActivationDescriptor_t activationDesc, + const void *alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) + - .. code-block:: cpp + + miopenStatus_t + miopenActivationBackward( + miopenHandle_t handle, + const miopenActivationDescriptor_t activDesc, + const void *alpha, + const miopenTensorDescriptor_t yDesc, + const void *y, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const miopenTensorDescriptor_t dxDesc, + void *dx) + +LRN operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnCreateLRNDescriptor( + cudnnLRNDescriptor_t *normDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenCreateLRNDescriptor( + miopenLRNDescriptor_t + *lrnDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnSetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned lrnN, + double lrnAlpha, + double lrnBeta, + double lrnK) + - .. code-block:: cpp + + miopenStatus_t + miopenSetLRNDescriptor( + const miopenLRNDescriptor_t lrnDesc, + miopenLRNMode_t mode, + unsigned lrnN, + double lrnAlpha, + double lrnBeta, + double lrnK) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnGetLRNDescriptor( + cudnnLRNDescriptor_t normDesc, + unsigned* lrnN, + double* lrnAlpha, + double* lrnBeta, + double* lrnK) + - .. code-block:: cpp + + miopenStatus_t + miopenGetLRNDescriptor( + const miopenLRNDescriptor_t lrnDesc, + miopenLRNMode_t *mode, + unsigned *lrnN, + double *lrnAlpha, + double *lrnBeta, + double *lrnK) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDestroyLRNDescriptor( + cudnnLRNDescriptor_t lrnDesc) + - .. code-block:: cpp + + miopenStatus_t + miopenDestroyLRNDescriptor( + miopenLRNDescriptor_t lrnDesc) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnLRNCrossChannelForward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t yDesc, + void *y) + - .. code-block:: cpp + + miopenStatus_t + miopenLRNForward( + miopenHandle_t handle, + const miopenLRNDescriptor_t lrnDesc, + const void *alpha, + const miopenTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const miopenTensorDescriptor_t yDesc, + void *y, + bool do_backward, + void *workspace) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnLRNCrossChannelBackward( + cudnnHandle_t handle, + cudnnLRNDescriptor_t normDesc, + cudnnLRNMode_t lrnMode, + const void* alpha, + const cudnnTensorDescriptor_t yDesc, + const void *y, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const void *beta, + const cudnnTensorDescriptor_t dxDesc, + void *dx) + - .. code-block:: cpp + + miopenStatus_t + miopenLRNBackward( + miopenHandle_t handle, + const miopenLRNDescriptor_t lrnDesc, + const void *alpha, + const miopenTensorDescriptor_t yDesc, + const void *y, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t xDesc, + const void *x, const void *beta, + const miopenTensorDescriptor_t dxDesc, + void *dx, + const void *workspace) + + * + - NA + - .. code-block:: cpp + + miopenStatus_t + miopenLRNGetWorkSpaceSize( + const miopenTensorDescriptor_t yDesc, + size_t *workSpaceSize) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnDeriveBNTensorDescriptor( + cudnnTensorDescriptor_t derivedBnDesc, + const cudnnTensorDescriptor_t xDesc, + cudnnBatchNormMode_t mode) + - .. code-block:: cpp + + miopenStatus_t + miopenDeriveBNTensorDescriptor( + miopenTensorDescriptor_t derivedBnDesc, + const miopenTensorDescriptor_t xDesc, + miopenBatchNormMode_t bn_mode) + +Batch normalization operations +------------------------------------------------------------------------------------------- + +.. list-table:: + :header-rows: 1 + + * + - cuDNN + - MIOpen + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnBatchNormalizationForwardTraining( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + void *alpha, + void *beta, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t yDesc, + void *y, + const cudnnTensorDescriptor_t + bnScaleBiasMeanVarDesc, + void *bnScale, + void *bnBias, + double exponentialAverageFactor, + void *resultRunningMean, + void *resultRunningVariance, + double epsilon, + void *resultSaveMean, + void *resultSaveInvVariance) + - .. code-block:: cpp + + miopenStatus_t + miopenBatchNormalizationForwardTraining( + miopenHandle_t handle, + miopenBatchNormMode_t bn_mode, + void *alpha, + void *beta, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenTensorDescriptor_t yDesc, + void *y, + const miopenTensorDescriptor_t + bnScaleBiasMeanVarDesc, + void *bnScale, + void *bnBias, + double expAvgFactor, + void *resultRunningMean, + void *resultRunningVariance, + double epsilon, + void *resultSaveMean, + void *resultSaveInvVariance) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnnBatchNormalizationForwardInference( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + void *alpha, + void *beta, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t yDesc, + void *y, + const cudnnTensorDescriptor_t + bnScaleBiasMeanVarDesc, + const void *bnScale, + void *bnBias, + const void *estimatedMean, + const void *estimatedVariance, + double epsilon) + - .. code-block:: cpp + + miopenStatus_t + miopenBatchNormalizationForwardInference( + miopenHandle_t handle, + miopenBatchNormMode_t bn_mode, + void *alpha, + void *beta, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenTensorDescriptor_t yDesc, + void *y, + const miopenTensorDescriptor_t + bnScaleBiasMeanVarDesc, + void *bnScale, + void *bnBias, + void *estimatedMean, + void *estimatedVariance, + double epsilon) + + * + - .. code-block:: cpp + + cudnnStatus_t + cudnnBatchNormalizationBackward( + cudnnHandle_t handle, + cudnnBatchNormMode_t mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const cudnnTensorDescriptor_t xDesc, + const void *x, + const cudnnTensorDescriptor_t dyDesc, + const void *dy, + const cudnnTensorDescriptor_t dxDesc, + void *dx, + const cudnnTensorDescriptor_t + bnScaleBiasDiffDesc, + const void *bnScale, + void *resultBnScaleDiff, + void *resultBnBiasDiff, + double epsilon, + const void *savedMean, + const void *savedInvVariance) + - .. code-block:: cpp + + miopenStatus_t + miopenBatchNormalizationBackward( + miopenHandle_t handle, + miopenBatchNormMode_t bn_mode, + const void *alphaDataDiff, + const void *betaDataDiff, + const void *alphaParamDiff, + const void *betaParamDiff, + const miopenTensorDescriptor_t xDesc, + const void *x, + const miopenTensorDescriptor_t dyDesc, + const void *dy, + const miopenTensorDescriptor_t dxDesc, + void *dx, + const miopenTensorDescriptor_t + bnScaleBiasDiffDesc, + const void *bnScale, + void *resultBnScaleDiff, + void *resultBnBiasDiff, + double epsilon, + const void *savedMean, + const void *savedInvVariance) diff --git a/docs/conf.py b/docs/conf.py index 50a7a1373f..88aa2d1141 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,7 @@ docs_core = ROCmDocs(left_nav_title) docs_core.run_doxygen(doxygen_root="doxygen", doxygen_path="doxygen/xml") +docs_core.enable_api_reference() docs_core.setup() external_projects_current_project = "miopen" diff --git a/docs/convolution.rst b/docs/convolution.rst deleted file mode 100644 index 6c26bdca23..0000000000 --- a/docs/convolution.rst +++ /dev/null @@ -1,156 +0,0 @@ - -Convolutional Layer -=================== - -The convolution layer API documentation - - -miopenConvolutionMode_t ------------------------ - -.. doxygenenum:: miopenConvolutionMode_t - -miopenConvFwdAlgorithm_t ------------------------- - -.. doxygenenum:: miopenConvFwdAlgorithm_t - -miopenConvBwdWeightsAlgorithm_t -------------------------------- - -.. doxygenenum:: miopenConvBwdWeightsAlgorithm_t - -miopenConvBwdDataAlgorithm_t ----------------------------- - -.. doxygenenum:: miopenConvBwdDataAlgorithm_t - -miopenConvAlgoPerf_t --------------------- - -.. doxygenstruct:: miopenConvAlgoPerf_t - -miopenConvSolution_t --------------------- - -.. doxygenstruct:: miopenConvSolution_t - -miopenCreateConvolutionDescriptor ---------------------------------- - -.. doxygenfunction:: miopenCreateConvolutionDescriptor - -miopenInitConvolutionDescriptor -------------------------------- - -.. doxygenfunction:: miopenInitConvolutionDescriptor - -miopenInitConvolutionNdDescriptor ---------------------------------- - -.. doxygenfunction:: miopenInitConvolutionNdDescriptor - -miopenGetConvolutionDescriptor ------------------------------- - -.. doxygenfunction:: miopenGetConvolutionDescriptor - -miopenGetConvolutionNdDescriptor --------------------------------- - -.. doxygenfunction:: miopenGetConvolutionNdDescriptor - -miopenGetConvolutionGroupCount ------------------------------- - -.. doxygenfunction:: miopenGetConvolutionGroupCount - -miopenSetConvolutionGroupCount ------------------------------- - -.. doxygenfunction:: miopenSetConvolutionGroupCount - -miopenSetTransposeConvOutputPadding ------------------------------------ - -.. doxygenfunction:: miopenSetTransposeConvOutputPadding - - -miopenSetTransposeConvNdOutputPadding -------------------------------------- - -.. doxygenfunction:: miopenSetTransposeConvNdOutputPadding - - -miopenGetConvolutionForwardOutputDim ------------------------------------- - -.. doxygenfunction:: miopenGetConvolutionForwardOutputDim - -miopenGetConvolutionNdForwardOutputDim --------------------------------------- - -.. doxygenfunction:: miopenGetConvolutionNdForwardOutputDim - -miopenConvolutionForwardGetWorkSpaceSize ----------------------------------------- - -.. doxygenfunction:: miopenConvolutionForwardGetWorkSpaceSize - -miopenFindConvolutionForwardAlgorithm -------------------------------------- - -.. doxygenfunction:: miopenFindConvolutionForwardAlgorithm - -miopenConvolutionForward ------------------------- - -.. doxygenfunction:: miopenConvolutionForward - -miopenConvolutionForwardBias ----------------------------- - -.. doxygenfunction:: miopenConvolutionForwardBias - -miopenFindConvolutionBackwardDataAlgorithm ------------------------------------------- - -.. doxygenfunction:: miopenFindConvolutionBackwardDataAlgorithm - -miopenConvolutionBackwardData ------------------------------ - -.. doxygenfunction:: miopenConvolutionBackwardData - -miopenConvolutionBackwardDataGetWorkSpaceSize ---------------------------------------------- - -.. doxygenfunction:: miopenConvolutionBackwardDataGetWorkSpaceSize - -miopenConvolutionBackwardWeightsGetWorkSpaceSize ------------------------------------------------- - -.. doxygenfunction:: miopenConvolutionBackwardWeightsGetWorkSpaceSize - -miopenFindConvolutionBackwardWeightsAlgorithm ---------------------------------------------- - -.. doxygenfunction:: miopenFindConvolutionBackwardWeightsAlgorithm - -miopenConvolutionBackwardWeights --------------------------------- - -.. doxygenfunction:: miopenConvolutionBackwardWeights - -miopenConvolutionBackwardBias ------------------------------ - -.. doxygenfunction:: miopenConvolutionBackwardBias - -miopenDestroyConvolutionDescriptor ----------------------------------- - -.. doxygenfunction:: miopenDestroyConvolutionDescriptor - - - diff --git a/docs/data/cba.png b/docs/data/how-to/cba.png similarity index 100% rename from docs/data/cba.png rename to docs/data/how-to/cba.png diff --git a/docs/data/driverTableCrop.png b/docs/data/how-to/driverTableCrop.png similarity index 100% rename from docs/data/driverTableCrop.png rename to docs/data/how-to/driverTableCrop.png diff --git a/docs/data/fp16fusions.png b/docs/data/how-to/fp16fusions.png similarity index 100% rename from docs/data/fp16fusions.png rename to docs/data/how-to/fp16fusions.png diff --git a/docs/data/fp32fusions.png b/docs/data/how-to/fp32fusions.png similarity index 100% rename from docs/data/fp32fusions.png rename to docs/data/how-to/fp32fusions.png diff --git a/docs/data/na.png b/docs/data/how-to/na.png similarity index 100% rename from docs/data/na.png rename to docs/data/how-to/na.png diff --git a/docs/datatypes.md b/docs/datatypes.md deleted file mode 100644 index cead3b9802..0000000000 --- a/docs/datatypes.md +++ /dev/null @@ -1,43 +0,0 @@ - -# Datatypes - - -MIOpen contains several datatypes at different levels of support. The enumerated datatypes are shown below: - -``` -typedef enum { - miopenHalf = 0, - miopenFloat = 1, - miopenInt32 = 2, - miopenInt8 = 3, - /* Value 4 is reserved. */ - miopenBFloat16 = 5, - miopenDouble = 6, - miopenFloat8 = 7, - miopenBFloat8 = 8 -} miopenDataType_t; -``` - -Of these types only `miopenFloat` and `miopenHalf` are fully supported across all layers in MIOpen. Please see the individual layers in API reference section for specific datatype support and limitations. - -Type descriptions: - * `miopenHalf` - 16-bit floating point - * `miopenFloat` - 32-bit floating point - * `miopenInt32` - 32-bit integer, used primarily for `int8` convolution outputs - * `miopenInt8` - 8-bit integer, currently only supported by `int8` convolution forward path, tensor set, tensor copy, tensor cast, tensor transform, tensor transpose, and im2col. - * `miopenBFloat16` - brain float fp-16 (8-bit exponent, 7-bit fraction), currently only supported by convolutions, tensor set, and tensor copy. - * `miopenDouble` - 64-bit floating point, supported only by reduction, layerNorm, batchNorm. - * `miopenFloat8` - 8-bit floating point (layout 1.4.3, exponent bias 7), currently only supported by convolutions. - * `miopenBFloat8` - 8-bit floating point (layout 1.5.2, exponent bias 15), currently only supported by convolutions. - - -Note: In addition to the standard datatypes above, pooling contains its own indexing datatypes: -``` -typedef enum { - miopenIndexUint8 = 0, - miopenIndexUint16 = 1, - miopenIndexUint32 = 2, - miopenIndexUint64 = 3, -} miopenIndexType_t; -``` - diff --git a/docs/doxygen/.gitignore b/docs/doxygen/.gitignore new file mode 100644 index 0000000000..9da623714e --- /dev/null +++ b/docs/doxygen/.gitignore @@ -0,0 +1,3 @@ +html/ +latex/ +xml/ diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 4744982cff..abb7207b62 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -44,14 +44,14 @@ PROJECT_NUMBER = v5.4.4 # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "AMD's Machine Intelligence Library" +PROJECT_BRIEF = "AMD's machine intelligence library" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is @@ -240,12 +240,6 @@ TAB_SIZE = 4 ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -819,6 +813,7 @@ FILE_PATTERNS = *.c \ *.hxx \ *.hpp \ *.h++ \ + *.l \ *.cs \ *.d \ *.php \ @@ -832,13 +827,19 @@ FILE_PATTERNS = *.c \ *.mm \ *.dox \ *.py \ - *.tcl \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ *.vhd \ *.vhdl \ *.ucf \ *.qsf \ - *.as \ - *.js + *.ice # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -954,7 +955,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = ../README.md +USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1072,13 +1073,6 @@ CLANG_OPTIONS = ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1129,7 +1123,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = ../_doxygen/header.html # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1139,7 +1133,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = ../_doxygen/footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1151,7 +1145,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = ../_doxygen/stylesheet.css # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1161,10 +1155,15 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = ../_doxygen/extra_stylesheet.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -2133,12 +2132,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2152,15 +2145,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = NO -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. @@ -2460,4 +2444,4 @@ DOT_CLEANUP = YES # Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. -WARN_AS_ERROR = YES +WARN_AS_ERROR = NO diff --git a/docs/driver.md b/docs/driver.md deleted file mode 100644 index 6785bc8970..0000000000 --- a/docs/driver.md +++ /dev/null @@ -1,9 +0,0 @@ -## Building the driver - -MIOpen provides an [application-driver](https://github.com/ROCm/MIOpen/tree/master/driver) which can be used to execute any one particular layer in isolation and measure performance and verification of the library. - -The driver can be built using the `MIOpenDriver` target: - -` cmake --build . --config Release --target MIOpenDriver ` **OR** ` make MIOpenDriver ` - -Documentation on how to run the driver is [here](https://github.com/ROCm/MIOpen/blob/develop/driver/README.md). diff --git a/docs/dropout.rst b/docs/dropout.rst deleted file mode 100644 index f5f52f97cc..0000000000 --- a/docs/dropout.rst +++ /dev/null @@ -1,92 +0,0 @@ - -Dropout Layer -============= - -The dropout layer API documentation - - -miopenRNGType_t ---------------- - -.. doxygenenum:: miopenRNGType_t - -miopenCreateDropoutDescriptor ------------------------------ - -.. doxygenfunction:: miopenCreateDropoutDescriptor - -miopenGetDropoutDescriptor --------------------------- - -.. doxygenfunction:: miopenGetDropoutDescriptor - -miopenRestoreDropoutDescriptor ------------------------------- - -.. doxygenfunction:: miopenRestoreDropoutDescriptor - -miopenDestroyDropoutDescriptor ------------------------------- - -.. doxygenfunction:: miopenDestroyDropoutDescriptor - -miopenSetDropoutDescriptor --------------------------- - -.. doxygenfunction:: miopenSetDropoutDescriptor - -miopenDropoutGetReserveSpaceSize --------------------------------- - -.. doxygenfunction:: miopenDropoutGetReserveSpaceSize - -miopenDropoutGetStatesSize --------------------------- - -.. doxygenfunction:: miopenDropoutGetStatesSize - -miopenDropoutForward --------------------- - -.. doxygenfunction:: miopenDropoutForward - -**Return value description:** - -* `miopenStatusSuccess` - No errors. - -* `miopenStatusBadParm` - Incorrect parameter detected. Check if the following conditions are met: - - - Input/Output dimension/element size/datatype does not match. - - - Tensor dimension is not in the range of 1D to 5D. - - - Noise shape is unsupported. - - - Dropout rate is not in the range of (0, 1]. - - - Insufficient state size for parallel PRNG. - - - Insufficient reservespace size. - - - Memory required by dropout forward configs exceeds GPU memory range. - -miopenDropoutBackward ---------------------- - -.. doxygenfunction:: miopenDropoutBackward - -**Return value description:** - -* `miopenStatusSuccess` - No errors. - -* `miopenStatusBadParm` - Incorrect parameter detected. Check if the following conditions are met: - - - Input/Output dimension/element size/datatype does not match. - - - Tensor dimension is not in the range of 1D to 5D. - - - Dropout rate is not in the range of (0, 1]. - - - Insufficient reservespace size. - - - Memory required by dropout backward configs exceeds GPU memory range. diff --git a/docs/embed.md b/docs/embed.md deleted file mode 100644 index f5cf0d9ae3..0000000000 --- a/docs/embed.md +++ /dev/null @@ -1,100 +0,0 @@ - -Building MIOpen for Embedded Systems -==================================== - - - -### Install dependencies -Install minimum dependencies (default location /usr/local): -``` -cmake -P install_deps.cmake --minimum --prefix /some/local/dir -``` - -Create build directory: -``` -mkdir build; cd build; -``` - -### Configuring for an embedded build -Minimal static build configuration line without embedded precompiled kernels package, or Find-Db: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BACKEND=HIP -DMIOPEN_EMBED_BUILD=On -DCMAKE_PREFIX_PATH="/some/local/dir" .. -``` - -To enable HIP kernels in MIOpen while using embedded builds add: `-DMIOPEN_USE_HIP_KERNELS=On` to the configure line. -For example: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BACKEND=HIP -DMIOPEN_USE_HIP_KERNELS=On -DMIOPEN_EMBED_BUILD=On -DCMAKE_PREFIX_PATH="/some/local/dir" .. -``` - - -### Embedding Find-Db and Performance database: -The Find-db provides a database of known convolution inputs. This allows user to have the best tuned kernels for their network. Embedding find-db requires a semi-colon separated list of architecture CU pairs to embed on-disk DBs in the binary; e.g., gfx906_60;gfx900_56. - -Example: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_EMBED_BUILD=On -DMIOPEN_EMBED_DB=gfx900_56 .. -``` - -This will configure the build directory for embedding not just the find-db, but also the performance database. - -### Embedding the precompiled kernels package: -To prevent the loss of performance due to compile time overhead, a build of MIOpen can take advantage of embedding the precompiled kernels package. The precompiled kernels package contains convolution kernels of known inputs and allows the user to avoid compiling kernels during runtime. - -### Embedding precompiled package - -#### Using a package install -To install the precompiled kernels package use the command: -``` -apt-get install miopenkernels-- -``` -Where `` is the GPU architecture (for example, gfx900, gfx906) and `` is the number of CUs available in the GPU (for example 56 or 64 etc). - -Not installing the precompiled kernel package would not impact the functioning of MIOpen, since MIOpen will compile these kernels on the target machine once the kernel is run, however, the compilation step may significantly increase the startup time for different operations. - -The script `utils/install_precompiled_kernels.sh` provided as part of MIOpen automates the above process, it queries the user machine for the GPU architecture and then installs the appropriate package. It may be invoked as: -``` -./utils/install_precompiled_kernels.sh -``` - -To embed the precompiled kernels package, configure cmake using the `MIOPEN_BINCACHE_PATH` -Example: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/path/to/package/install -DMIOPEN_EMBED_BUILD=On .. -``` - -#### Using the URL to a kernels binary -Alternatively, the flag `MIOPEN_BINCACHE_PATH` can be used with a URL that contains the binary. -Example: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/URL/to/binary -DMIOPEN_EMBED_BUILD=On .. -``` - -Precompiled kernels packages are installed in `/opt/rocm/miopen/share/miopen/db`. -An example with the architecture gfx900 with 56 compute units: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/opt/rocm/miopen/share/miopen/db/gfx900_56.kdb -DMIOPEN_EMBED_BUILD=On .. -``` - - -As of ROCm 3.8 / MIOpen 2.7 precompiled kernels binaries are located at [repo.radeon.com](http://repo.radeon.com/rocm/miopen-kernel/) -For example for the architecture gfx906 with 64 compute units: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=http://repo.radeon.com/rocm/miopen-kernel/rel-3.8/gfx906_60.kdb -DMIOPEN_EMBED_BUILD=On .. -``` - -### Full configuration line: -Putting it all together, building MIOpen statically, and embedding the performance database, find-db, and the precompiled kernels binary: -``` -CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/path/to/package/install -DMIOPEN_EMBED_BUILD=On -DMIOPEN_EMBED_DB=gfx900_56 .. -``` - -After configuration is complete, run: -``` -make -j -``` - - - - - diff --git a/docs/find_and_immediate.md b/docs/find_and_immediate.md deleted file mode 100644 index 69d4d999dd..0000000000 --- a/docs/find_and_immediate.md +++ /dev/null @@ -1,189 +0,0 @@ -Find and Immediate Mode -======================= - - - -## Find API - -MIOpen contains several convolution algorithms for each stage of training or inference. Pre-MIOpen version 2.0 users needed to call Find methods in order generate a set of applicable algorithms. - -A typical workflow for the find stage: - -``` -miopenConvolutionForwardGetWorkSpaceSize(handle, - weightTensorDesc, - inputTensorDesc, - convDesc, - outputTensorDesc, - &maxWorkSpaceSize); - -// < allocate workspace > - - -// NOTE: -// miopenFindConvolution*() call is expensive in terms of execution time and required workspace. -// Therefore it is highly recommended to save off the selected algorithm and workspace required so that -// can be reused later within the lifetime of the same MIOpen handle object. -// In this way, there should be is no need to invoke miopenFind*() more than once per application lifetime. - -miopenFindConvolutionForwardAlgorithm(handle, - inputTensorDesc, - input_device_mem, - weightTensorDesc, - weight_device_mem, - convDesc, - outputTensorDesc, - output_device_mem,, - request_algo_count, - &ret_algo_count, - perf_results, - workspace_device_mem, - maxWorkSpaceSize, - 1); - -// < select fastest algorithm > - -// < free previously allocated workspace and allocate workspace required for the selected algorithm> - -miopenConvolutionForward(handle, &alpha, - inputTensorDesc, - input_device_mem, - weightTensorDesc, - weight_device_mem, - convDesc, - perf_results[0].fwd_algo, // use the fastest algo - &beta, - outputTensorDesc, - output_device_mem, - workspace_device_mem, - perf_results[0].memory); //workspace size -``` - - -The results of Find() are returned in an array of `miopenConvAlgoPerf_t` structs in order of performance, with the fastest at index 0. - -This call sequence is executed once per session as it is inherently expensive. Of those, `miopenFindConvolution*()` is the most expensive call. It caches its own results on disk, so the subsequent calls during the same MIOpen session will execute faster. However, it is better to remember results of `miopenFindConvolution*()` in the application, as recommended above. - -Internally MIOpen's Find calls will compile and benchmark a set of `solvers` contained in `miopenConvAlgoPerf_t` this is done in parallel per `miopenConvAlgorithm_t`. The level of parallelism can be controlled using an environment variable. See the debugging section [controlling parallel compilation](https://rocm.docs.amd.com/projects/MIOpen/en/latest/DebugAndLogging.html#controlling-parallel-compilation) for more details. - - -## Immediate Mode API - -MIOpen v2.0 introduces the immediate which removes the requirement for the `miopenFindConvolution*()` calls and their associated runtime costs. In this mode, the user can query the MIOpen runtime for all the supported _solutions_ for a given convolution configuration. These solutions may either be using the same algorithm or different ones. The sequence of operations for in immediate mode is similar to launching regular convolutions in MIOpen i.e. through the use of the `miopenFindConvolution*()` API. However, in this case the different APIs have much lower runtime cost. A typical convolution call would be similar to the following sequence of calls: - -* The user constructs the MIOpen handle and relevant descriptors such as the convolution descriptor as usual. -* With the above data structures, the user calls `miopenConvolution*GetSolutionCount` to get the **maximum** number of supported solutions for the convolution descriptor in question. -* The count obtained above is used to allocate memory for the `miopenConvSolution_t` structure introduced in MIOpen v2.0 -* The user calls `miopenConvolution*GetSolution` to populate the `miopenConvSolution_t` structures allocated above. The returned list is ordered in the order of best performance, thus the first element would be the fastest. -* While the above structure returns the amount of workspace required for an algorithm, the user may inquire the amount of a workspace required for a known solution id by using the `miopenConvolution*GetSolutionWorkspaceSize` API call. However, this is not a requirement, since the strucure returned by `miopenConvolution*GetSolution` would already have this information. -* Now the user may initiate the convolution operation in _immediate_ mode by calling `miopenConvolution*Immediate`. Which would populate the output tensor descriptor with the respective convolution result. However, the first call to `miopenConvolution*Immediate` may consume more time since the kernel may not be present in the kernel cache and may need to be compiled. -* Optionally, the user may compile the solution of choice by calling `miopenConvolution*CompileSolution` which would ensure that the kernel represented by the chosen solution is populated in the kernel cache a priori, removing the necessity for compiling the kernel in question. - - -``` -miopenConvolutionForwardGetSolutionCount(handle, - weightTensorDesc, - inputTensorDesc, - convDesc, - outputTensorDesc, - &solutionCount); - - -// < allocate an array of miopenConvSolution_t of size solutionCount > - - -miopenConvolutionForwardGetSolution(handle, - weightTensorDesc, - inputTensorDesc, - convDesc, - outputTensorDesc, - solutionCount, - &actualCount, - solutions); - -// < select a solution from solutions array > - -miopenConvolutionForwardGetSolutionWorkspaceSize(handle, - weightTensorDesc, - inputTensorDesc, - convDesc, - outputTensorDesc, - selected->solution_id, - &ws_size); - -// < allocate solution workspace of size ws_size > - - -// This stage is optional -miopenConvolutionForwardCompileSolution(handle, - weightTensorDesc, - inputTensorDesc, - convDesc, - outputTensorDesc, - selected->solution_id); - - - - miopenConvolutionForwardImmediate(handle, - weightTensor, - weight_device_mem, - inputTensorDesc, - input_device_mem, - convDesc, - outputTensorDesc, - output_device_mem, - workspace_device_mem, - ws_size, - selected->solution_id); -``` - -## Immediate Mode Fallback - -The immediate mode is underpinned by the [Find-Db](https://rocm.docs.amd.com/projects/MIOpen/en/latest/finddb.html), however it may not contain every configuration of interest. If Find-Db encounters a database miss it has two fallback paths it can take, depending on whether the cmake variable MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK is set to ON or OFF. However, if the user requires the best possible performance they should run the Find stage at least once. - -### 1. AI-based Heuristic Fallback (Default) - -If MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK is set to ON, which it is by default, Immediate Mode's behavior on a database miss is to use an AI-based heurisitic to pick the optimal solution. First, the applicability of the AI-based heuristic for the given configuration is checked. If the heuristic is applicable, it feeds various parameters of the given configuration into a neural network which has been tuned to predict the optimal solution with 90% accuracy. - -### 2. Weighted Throughput Index Based Fallback - -When MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK is set to OFF, or the AI Heuristic is not applicable for the given convolution configuration, Immediate mode's behavior on encountering a database miss is to use a Weighted Thoughput Index (WTI) based mechanism to estimate which solution would be optimal based upon parameters of the convolution configuration. - - - -## Limitations of Immediate Mode - -### Architectual Limitations -The system Find-Db has only been populated for the following architectures: - * gfx906 with 64 CUs - * gfx906 with 60 CUs - * gfx900 with 64 CUs - * gfx900 with 56 CUs - -If the user's architecture is not listed above they will need to run the Find API once on their system per application in order to take advantage of immediate mode's more efficient behavior. - - -### Backend Limitations - -OpenCL support for immediate mode via the fallback is limited to fp32 datatypes. This is because this current release's fallback path goes through GEMM which on the OpenCL is serviced through MIOpenGEMM -- which itself only contains support for fp32. The HIP backend uses rocBLAS as its fallback path which contains a richer set of datatypes. - - -### Find Modes - -MIOpen provides a set of Find modes which are used to accelerate the Find calls. The different modes are set by using the environment variable `MIOPEN_FIND_MODE`, and setting it to one of the values: - -- `NORMAL`, or `1`: Normal Find: This is the full Find mode call, which will benchmark all the solvers and return a list. -- `FAST`, or `2`: Fast Find: Checks the [Find-Db](https://rocm.docs.amd.com/projects/MIOpen/en/latest/finddb.html) for an entry. If there is a Find-Db hit, use that entry. If there is a miss, utilize the Immediate mode fallback. If Start-up times are expected to be faster, but worse GPU performance. -- `HYBRID`, or `3`, or unset `MIOPEN_FIND_MODE`: Hybrid Find: Checks the [Find-Db](https://rocm.docs.amd.com/projects/MIOpen/en/latest/finddb.html) for an entry. If there is a Find-Db hit, use that entry. If there is a miss, use the existing Find machinery. Slower start-up times than Fast Find, but no GPU performance drop. -- `4`: This value is reserved and should not be used. -- `DYNAMIC_HYBRID`, or `5`: Dynamic Hybrid Find: Checks the [Find-Db](https://rocm.docs.amd.com/projects/MIOpen/en/latest/finddb.html) for an entry. If there is a Find-Db hit, uses that entry. If there is a miss, uses the existing Find machinery with skipping non-dynamic kernels. Faster start-up times than Hybrid Find, but GPU performance may be a bit worse. - - Currently, the default Find mode is `DYNAMIC_HYBRID`. To run the full `NORMAL` Find mode, set the environment as: - ``` - export MIOPEN_FIND_MODE=NORMAL - ``` - Or, - ``` - export MIOPEN_FIND_MODE=1 - ``` - diff --git a/docs/finddb.md b/docs/finddb.md deleted file mode 100644 index 602835cea0..0000000000 --- a/docs/finddb.md +++ /dev/null @@ -1,44 +0,0 @@ -Find-Db Database -================ - -Prior to MIOpen 2.0, users utilized calls such as `miopenFindConvolution*Algorithm()` to gather a set of convolution algorithms in the form of an array of `miopenConvSolution_t` structs. This process is time consuming because it requires online benchmarking of competing algorithms. In MIOpen 2.0 an [immediate mode](https://rocm.docs.amd.com/projects/MIOpen/en/latest/find_and_immediate.html) is introduced. - -Immediate mode is based on a database which contains the results of calls to the legacy Find() stage. This database is called `Find-Db`. It consists of two parts: -- **System Find-Db**, a system-wide storage which holds the pre-run values for the most applicable configurations, -- **User Find-Db**, a per-user storage which is intended to hold results for arbitrary user-run configurations. It also performs double duty as a cache for the Find() stage. - -The User Find-Db **always takes precedence** over System Find-Db. - -By default, System Find-Db resides within MIOpen's install location, while User Find-Db resides in the user's home directory. See [Setting up locations](https://github.com/ROCm/MIOpen#setting-up-locations) for more information. - - * The System Find-Db is *not* modified upon installation of MIOpen. - * There are separate Find databases for HIP and OpenCL backends. - -### Populating the User Find-Db - -MIOpen collects Find-db information during the following MIOpen API calls: -- `miopenFindConvolutionForwardAlgorithm()` -- `miopenFindConvolutionBackwardDataAlgorithm()` -- `miopenFindConvolutionBackwardWeightsAlgorithm()` - -During the call, find data entries are collected for one _problem configuration_ (implicitly defined by the tensor descriptors and convolution descriptor passed to API function). - - -### Updating MIOpen and the User Find-Db - -When the user installs a new version of MIOpen, the new version of MIOpen will _ignore_ old **User find-db*** files. Thus, the user is _not required_ to move or delete their old User find-db files. However, the user may wish to re-collect the information into their brand new **User find-db**. This should be done in the same way as it was done with the previous version of the library -- _if_ it was done. This would keep Immediate mode optimized. - - -### Disabling Find-Db - -By default MIOpen will use the Find-Db. Users can disable the Find-Db by setting the environmental variable `MIOPEN_DEBUG_DISABLE_FIND_DB` to 1: -``` -export MIOPEN_DEBUG_DISABLE_FIND_DB=1 -``` - -**Note:** The System Find-Db has the ability to be cached into memory and may increase performance dramatically. To disable this option use the cmake configuration flag: -``` --DMIOPEN_DEBUG_FIND_DB_CACHING=Off -``` - - diff --git a/docs/fusion.rst b/docs/fusion.rst deleted file mode 100644 index f4c2e382f2..0000000000 --- a/docs/fusion.rst +++ /dev/null @@ -1,97 +0,0 @@ - - -Layer Fusion -============ - -GPU kernel fusion API reference - -miopenFusionDirection_t ------------------------ - -.. doxygenenum:: miopenFusionDirection_t - -miopenCreateFusionPlan ----------------------- - -.. doxygenfunction:: miopenCreateFusionPlan - - -miopenDestroyFusionPlan ------------------------ - -.. doxygenfunction:: miopenDestroyFusionPlan - - -miopenCompileFusionPlan ------------------------ - -.. doxygenfunction:: miopenCompileFusionPlan - -miopenFusionPlanGetOp ---------------------- - -.. doxygenfunction:: miopenFusionPlanGetOp - -miopenFusionPlanGetWorkSpaceSize --------------------------------- - -.. doxygenfunction:: miopenFusionPlanGetWorkSpaceSize - -miopenFusionPlanConvolutionGetAlgo ----------------------------------- - -.. doxygenfunction:: miopenFusionPlanConvolutionGetAlgo - -miopenCreateOpConvForward -------------------------- - -.. doxygenfunction:: miopenCreateOpConvForward - -miopenCreateOpActivationForward -------------------------------- - -.. doxygenfunction:: miopenCreateOpActivationForward - -miopenCreateOpBiasForward -------------------------- - -.. doxygenfunction:: miopenCreateOpBiasForward - -miopenCreateOpBatchNormInference --------------------------------- - -.. doxygenfunction:: miopenCreateOpBatchNormInference - -miopenCreateOperatorArgs ------------------------- - -.. doxygenfunction:: miopenCreateOperatorArgs - -miopenDestroyOperatorArgs -------------------------- - -.. doxygenfunction:: miopenDestroyOperatorArgs - -miopenSetOpArgsConvForward --------------------------- - -.. doxygenfunction:: miopenSetOpArgsConvForward - -miopenSetOpArgsBatchNormInference ---------------------------------- - -.. doxygenfunction:: miopenSetOpArgsBatchNormInference - -miopenSetOpArgsBiasForward --------------------------- - -.. doxygenfunction:: miopenSetOpArgsBiasForward - -miopenExecuteFusionPlan ------------------------ - -.. doxygenfunction:: miopenExecuteFusionPlan - - - - diff --git a/docs/groupnorm.rst b/docs/groupnorm.rst deleted file mode 100644 index d61eb1ac9e..0000000000 --- a/docs/groupnorm.rst +++ /dev/null @@ -1,20 +0,0 @@ - -GroupNorm Layer(experimental) -============================= - -The groupnorm types and functions. -It splits input channels into num_group groups and do normalize for each group. - -To enable this, define MIOPEN_BETA_API before including miopen.h. - - -miopenNormMode_t ------------------------ - -.. doxygenenum:: miopenNormMode_t - -miopenGroupNormForward ----------------------------------- - -.. doxygenfunction:: miopenGroupNormForward - diff --git a/docs/handle.rst b/docs/handle.rst deleted file mode 100644 index b5b3fd4618..0000000000 --- a/docs/handle.rst +++ /dev/null @@ -1,47 +0,0 @@ - -Handle -====== - -Handle types and functions. - - -miopenStatus_t --------------- - -.. doxygenenum:: miopenStatus_t - -miopenCreate ------------- - -.. doxygenfunction:: miopenCreate - -miopenCreateWithStream ----------------------- - -.. doxygenfunction:: miopenCreateWithStream - -miopenDestroy -------------- - -.. doxygenfunction:: miopenDestroy - -miopenSetStream ---------------- - -.. doxygenfunction:: miopenSetStream - -miopenGetStream ---------------- - -.. doxygenfunction:: miopenGetStream - -miopenGetKernelTime -------------------- - -.. doxygenfunction:: miopenGetKernelTime - -miopenEnableProfiling ---------------------- - -.. doxygenfunction:: miopenEnableProfiling - diff --git a/docs/how-to/debug-log.rst b/docs/how-to/debug-log.rst new file mode 100644 index 0000000000..cfd28873ce --- /dev/null +++ b/docs/how-to/debug-log.rst @@ -0,0 +1,393 @@ +.. meta:: + :description: MIOpen documentation + :keywords: MIOpen, ROCm, API, documentation + +******************************************************************** +Logging & debugging +******************************************************************** + +All logging messages are output to the standard error stream (``stderr``). You can use the following +environmental variables to control logging. Both variables are disabled by default. + +* ``MIOPEN_ENABLE_LOGGING``: Print the basic layer-by-layer MIOpen API call + information with actual parameters (configurations). This information is important for debugging. + + * To enable feature: ``1``, ``on``, ``yes``, ``true``, ``enable``, ``enabled`` + * To disable feature: ``0``, ``off``, ``no``, ``false``, ``disable``, ``disabled`` + +* ``MIOPEN_ENABLE_LOGGING_CMD``: Output the associated ``MIOpenDriver`` command lines into the + console. + + * To enable feature: ``1``, ``on``, ``yes``, ``true``, ``enable``, ``enabled`` + * To disable feature: ``0``, ``off``, ``no``, ``false``, ``disable``, ``disabled`` + +* ``MIOPEN_LOG_LEVEL``: In addition to API call information and driver commands, MIOpen prints + information related to the progress of its internal operations. This information can be useful for + debugging and understanding the library's principles of operation. `MIOPEN_LOG_LEVEL` controls the + verbosity of these messages. Allowed values are: + + * ``0``: Default. Works as level 4 for release builds and level 5 for debug builds. + * ``1``: Quiet. No logging messages. + * ``2``: Fatal errors only (not used yet). + * ``3``: Errors and fatals. + * ``4``: All errors and warnings. + * ``5``: Info. All the preceding information, plus information for debugging purposes. + * ``6``: Detailed information. All the preceding information, plus more detailed information for + debugging. + * ``7``: Trace. All the preceding information, plus additional details. + +* ``MIOPEN_ENABLE_LOGGING_MPMT``: When enabled, each log line is prefixed with information you + can use to identify records printed from different processes or threads. This is useful for debugging + multi-process/multi-threaded applications. + +* ``MIOPEN_ENABLE_LOGGING_ELAPSED_TIME``: Adds a timestamp to each log line that indicates the + time elapsed (in milliseconds) since the previous log message. + +.. tip:: + + If you require technical support, include the console log that is produced from: + + .. code:: cpp + + export MIOPEN_ENABLE_LOGGING=1 + export MIOPEN_ENABLE_LOGGING_CMD=1 + export MIOPEN_LOG_LEVEL=6 + +Layer filtering +=================================================== + +The following sections contain environment variables that you can use to enable or disable various +kinds of kernels and algorithms. These are helpful for debugging MIOpen and framework integrations. + +For these environment variables, you can use the following values: + +* To enable kernel/algorithm: ``1``, ``yes``, ``true``, ``enable``, ``enabled`` +* To disable kernel/algorithm: ``0``, ``no``, ``false``, ``disable``, ``disabled`` + +.. warning:: + + When you use the library with layer filtering, the results of ``*Find()`` calls become narrower than + during normal operation. This means that relevant FindDb entries won't include all the solutions that + are normally there. Therefore, the subsequent immediate mode ``*Get()`` calls may return incomplete + information or run into fallback path. + +In order to rehabilitate immediate mode, you can: + +* Re-enable all solvers and re-run the same ``*Find()`` calls you previously ran +* Completely remove the User FindDb + +If no variable is set, MIOpen behaves as if the variable is set to ``enabled``. This means that kernels and +algorithms are enabled by default. + +Filtering by algorithm +-------------------------------------------------------------------------------------------------------------- + +These variables control the sets (families) of convolution solutions. For example, the direct algorithm +is implemented in several solutions that use OpenCL and GCN assembly. The corresponding variable +can disable them all. + +* ``MIOPEN_DEBUG_CONV_FFT``: FFT convolution algorithm. +* ``MIOPEN_DEBUG_CONV_DIRECT``: Direct convolution algorithm. +* ``MIOPEN_DEBUG_CONV_GEMM``: GEMM convolution algorithm. +* ``MIOPEN_DEBUG_CONV_WINOGRAD``: Winograd convolution algorithm. +* ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM``: Implicit GEMM convolution algorithm. + +Filtering by build method +-------------------------------------------------------------------------------------------------------------- + +* ``MIOPEN_DEBUG_GCN_ASM_KERNELS``: Kernels written in assembly language. These are used in + many convolutions (some direct solvers, Winograd kernels, and fused convolutions) and batch + normalization. +* ``MIOPEN_DEBUG_HIP_KERNELS``: Convolution kernels written in HIP. These implement the + ImplicitGemm algorithm. +* ``MIOPEN_DEBUG_OPENCL_CONVOLUTIONS``: Convolution kernels written in OpenCL; this only + affects convolutions. +* ``MIOPEN_DEBUG_AMD_ROCM_PRECOMPILED_BINARIES``: Binary kernels. The library does not + currently use binaries. + +Filtering out all but one solution +-------------------------------------------------------------------------------------------------------------- + +* ``MIOPEN_DEBUG_FIND_ONLY_SOLVER=solution_id``: Directly affects only ``*Find()`` calls. However, + there is an indirect connection to immediate mode (per the previous warning). + + * ``solution_id`` must be a numeric or a string identifier of some solution. + * If ``solution_id`` denotes some applicable solution, then only that solution is found (in addition to + GEMM and FFT, if applicable--refer to the following note). + * If ``solution_id`` is valid, but not applicable, then ``*Find()`` fails with all algorithms (except for GEMM + and FFT,--refer to the following note). + * Otherwise, the ``solution_id`` is invalid (i.e., it doesn't match any existing solution) and the ``*Find()`` + call fails. + +.. note:: + + This environmental variable doesn't affect the GEMM and FFT solutions. For now, GEMM and FFT can + only be disabled at the algorithm level. + +Filtering the solutions on an individual basis +-------------------------------------------------------------------------------------------------------------- + +Some of the solutions have individual controls, which affect both find and immediate modes. + +* Direct solutions: + + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_3X3U`` -- ``ConvAsm3x3U`` + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_1X1U`` -- ``ConvAsm1x1U`` + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_1X1UV2`` -- ``ConvAsm1x1UV2`` + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_5X10U2V2`` -- ``ConvAsm5x10u2v2f1`, `ConvAsm5x10u2v2b1`` + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_7X7C3H224W224`` -- ``ConvAsm7x7c3h224w224k64u2v2p3q3f1`` + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_WRW3X3`` -- ``ConvAsmBwdWrW3x3`` + * ``MIOPEN_DEBUG_CONV_DIRECT_ASM_WRW1X1`` -- ``ConvAsmBwdWrW1x1`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_FWD11X11`` -- ``ConvOclDirectFwd11x11`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_FWDGEN`` -- ``ConvOclDirectFwdGen`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_FWD`` -- ``ConvOclDirectFwd`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_FWD1X1`` -- ``ConvOclDirectFwd1x1`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_WRW2`` -- ``ConvOclBwdWrW2`` (where n = + ``{1,2,4,8,16}``) and ``ConvOclBwdWrW2NonTunable`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_WRW53`` -- ``ConvOclBwdWrW53`` + * ``MIOPEN_DEBUG_CONV_DIRECT_OCL_WRW1X1`` -- ``ConvOclBwdWrW1x1`` + +* Winograd solutions: + + * ``MIOPEN_DEBUG_AMD_WINOGRAD_3X3`` -- ``ConvBinWinograd3x3U``, FP32 Winograd Fwd/Bwd, + filter size fixed to 3x3 + * ``MIOPEN_DEBUG_AMD_WINOGRAD_RXS`` -- ``ConvBinWinogradRxS``, FP32/FP16 F(3,3) Fwd/Bwd + and FP32 F(3,2) WrW Winograd. Subsets: + + * ``MIOPEN_DEBUG_AMD_WINOGRAD_RXS_WRW`` -- FP32 F(3,2) WrW convolutions only + * ``MIOPEN_DEBUG_AMD_WINOGRAD_RXS_FWD_BWD`` -- FP32/FP16 F(3,3) Fwd/Bwd + + * ``MIOPEN_DEBUG_AMD_WINOGRAD_RXS_F3X2`` -- ``ConvBinWinogradRxSf3x2``, FP32/FP16 + Fwd/Bwd F(3,2) Winograd + * ``MIOPEN_DEBUG_AMD_WINOGRAD_RXS_F2X3`` -- ``ConvBinWinogradRxSf2x3``, FP32/FP16 + Fwd/Bwd F(2,3) Winograd, serves group convolutions only + * ``MIOPEN_DEBUG_AMD_WINOGRAD_RXS_F2X3_G1`` -- ``ConvBinWinogradRxSf2x3g1``, FP32/FP16 + Fwd/Bwd F(2,3) Winograd, for non-group convolutions + +* Multi-pass Winograd: + + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X2`` -- ``ConvWinograd3x3MultipassWrW<3-2>``, + WrW F(3,2), stride 2 only + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X3`` -- ``ConvWinograd3x3MultipassWrW<3-3>``, + WrW F(3,3), stride 2 only + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X4`` -- ``ConvWinograd3x3MultipassWrW<3-4>``, + WrW F(3,4) + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X5`` -- ``ConvWinograd3x3MultipassWrW<3-5>``, + WrW F(3,5) + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F3X6`` -- ``ConvWinograd3x3MultipassWrW<3-6>``, + WrW F(3,6) + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F5X3`` -- ``ConvWinograd3x3MultipassWrW<5-3>``, + WrW F(5,3) + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F5X4`` -- ``ConvWinograd3x3MultipassWrW<5-4>``, + WrW F(5,4) + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F7X2``: + + * ``ConvWinograd3x3MultipassWrW<7-2>``, WrW F(7,2) + * ``ConvWinograd3x3MultipassWrW<7-2-1-1>``, WrW F(7x1,2x1) + * ``ConvWinograd3x3MultipassWrW<1-1-7-2>``, WrW F(1x7,1x2) + + * ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_F7X3``: + + * ``ConvWinograd3x3MultipassWrW<7-3>``, WrW F(7,3) + * ``ConvWinograd3x3MultipassWrW<7-3-1-1>``, WrW F(7x1,3x1) + * ``ConvWinograd3x3MultipassWrW<1-1-7-3>``, WrW F(1x7,1x3) + + * ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F2X3`` -- ``ConvMPBidirectWinograd<2-3>``, + FWD/BWD F(2,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F3X3`` -- ``ConvMPBidirectWinograd<3-3>``, + FWD/BWD F(3,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F4X3`` -- ``ConvMPBidirectWinograd<4-3>``, + FWD/BWD F(4,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F5X3`` -- ``ConvMPBidirectWinograd<5-3>``, + FWD/BWD F(5,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_F6X3`` -- ``ConvMPBidirectWinograd<6-3>``, + FWD/BWD F(6,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F2X3`` -- + ``ConvMPBidirectWinograd_xdlops<2-3>``, FWD/BWD F(2,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F3X3`` -- + ``ConvMPBidirectWinograd_xdlops<3-3>``, FWD/BWD F(3,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F4X3`` -- + ``ConvMPBidirectWinograd_xdlops<4-3>``, FWD/BWD F(4,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F5X3`` -- + ``ConvMPBidirectWinograd_xdlops<5-3>``, FWD/BWD F(5,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_XDLOPS_WINOGRAD_F6X3`` -- + ``ConvMPBidirectWinograd_xdlops<6-3>``, FWD/BWD F(6,3) + * ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_EXPEREMENTAL_FP16_TRANSFORM`` -- + ``ConvMPBidirectWinograd*``, FWD/BWD FP16 experimental mode (use at your own risk). Disabled + by default. + * ``MIOPEN_DEBUG_AMD_FUSED_WINOGRAD`` -- Fused FP32 F(3,3) Winograd, variable filter size. + +Implicit GEMM solutions: + +* ASM implicit GEMM + + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_FWD_V4R1`` -- + ``ConvAsmImplicitGemmV4R1DynamicFwd`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_FWD_V4R1_1X1`` -- + ``ConvAsmImplicitGemmV4R1DynamicFwd_1x1`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_BWD_V4R1`` -- + ``ConvAsmImplicitGemmV4R1DynamicBwd`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_WRW_V4R1`` -- + ``ConvAsmImplicitGemmV4R1DynamicWrw`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_FWD_GTC_XDLOPS`` -- + ``ConvAsmImplicitGemmGTCDynamicFwdXdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_BWD_GTC_XDLOPS`` -- + ``ConvAsmImplicitGemmGTCDynamicBwdXdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_ASM_WRW_GTC_XDLOPS`` -- + ``ConvAsmImplicitGemmGTCDynamicWrwXdlops`` + +* HIP implicit GEMM + + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R1`` -- + ``ConvHipImplicitGemmV4R1Fwd`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R4`` -- + ``ConvHipImplicitGemmV4R4Fwd`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V1R1`` -- + ``ConvHipImplicitGemmBwdDataV1R1`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V4R1`` -- + ``ConvHipImplicitGemmBwdDataV4R1`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R1`` -- + ``ConvHipImplicitGemmV4R1WrW`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R4`` -- + ``ConvHipImplicitGemmV4R4WrW`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R4_XDLOPS`` -- + ``ConvHipImplicitGemmForwardV4R4Xdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R5_XDLOPS`` -- + ``ConvHipImplicitGemmForwardV4R5Xdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V1R1_XDLOPS`` -- + ``ConvHipImplicitGemmBwdDataV1R1Xdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_BWD_V4R1_XDLOPS`` -- + ``ConvHipImplicitGemmBwdDataV4R1Xdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R4_XDLOPS`` -- + ``ConvHipImplicitGemmWrwV4R4Xdlops`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_FWD_V4R4_PADDED_GEMM_XDLOPS`` -- + ``ConvHipImplicitGemmForwardV4R4Xdlops_Padded_Gemm`` + * ``MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_WRW_V4R4_PADDED_GEMM_XDLOPS`` -- + ``ConvHipImplicitGemmWrwV4R4Xdlops_Padded_Gemm`` + +rocBlas logging and behavior +========================================================== + +The ``ROCBLAS_LAYER`` environmental variable can be set to output GEMM information: + +* ``ROCBLAS_LAYER=``: Not set--there is no logging +* ``ROCBLAS_LAYER=1``: Trace logging +* ``ROCBLAS_LAYER=2``: Bench logging +* ``ROCBLAS_LAYER=3``: Trace and bench logging + +You can also set the ``MIOPEN_GEMM_ENFORCE_BACKEND`` environment variable to override the +default GEMM backend (rocBLAS): + +* ``MIOPEN_GEMM_ENFORCE_BACKEND=1``: Use rocBLAS if enabled +* ``MIOPEN_GEMM_ENFORCE_BACKEND=2``: Reserved +* ``MIOPEN_GEMM_ENFORCE_BACKEND=3``: No GEMM is called +* ``MIOPEN_GEMM_ENFORCE_BACKEND=4``: Reserved +* ``MIOPEN_GEMM_ENFORCE_BACKEND=``: Use default behavior + +To disable using rocBlas entirely, set the `-DMIOPEN_USE_ROCBLAS=Off` configuration flag during +MIOpen configuration. + +You can find more information on logging with rocBLAS in the +:doc:`rocBLAS programmer guide `. + +Numerical checking +========================================================== + +You can use the ``MIOPEN_CHECK_NUMERICS`` environmental variable to debug potential numerical +abnormalities. Setting this variable scans all inputs and outputs of each kernel called and attempts to +detect infinities (infs), not-a-number (NaN), and all zeros. This environment variable has several +settings that help with debugging: + +* ``MIOPEN_CHECK_NUMERICS=0x01``: Fully informative. Prints results from all checks to console. +* ``MIOPEN_CHECK_NUMERICS=0x02``: Warning information. Prints results only if an abnormality is + detected. +* ``MIOPEN_CHECK_NUMERICS=0x04``: Throw error on detection. MIOpen runs ``MIOPEN_THROW`` + upon abnormal result. +* ``MIOPEN_CHECK_NUMERICS=0x08``: Abort upon abnormal result. Allows you to drop into a + debugging session. +* ``MIOPEN_CHECK_NUMERICS=0x10``: Print stats. Computes and prints mean/absmean/min/max + (note that this is slow). + +.. _control-parallel-compilation: + +Controlling parallel compilation +========================================================== + +MIOpen's convolution ``*Find()`` calls compile and benchmark a set of ``solvers`` contained in +``miopenConvAlgoPerf_t``. This is done in parallel per ``miopenConvAlgorithm_t``. Parallelism per +algorithm is set to 20 threads. Typically, there are far fewer threads spawned due to the limited number +of kernels under any given algorithm. + +You can control the level of parallelism using the ``MIOPEN_COMPILE_PARALLEL_LEVEL`` environment +variable. + +To disable multi-threaded compilation, run: + +.. code:: cpp + + export MIOPEN_COMPILE_PARALLEL_LEVEL=1 + +Experimental controls +========================================================== + +Using experimental controls may result in: + +* Performance drops +* Computation inaccuracies +* Runtime errors +* Other kinds of unexpected behavior + +We strongly recommended only using these controls at the explicit request of the library developers. + +Code Object version selection (experimental) +------------------------------------------------------------------------------------------------------------- + +Different ROCm versions use Code Object (CO) files from different versions (i.e., formats). The library +automatically uses the most suitable version. The following variables allow for experimenting and +triaging possible problems related to CO version: + +* ``MIOPEN_DEBUG_AMD_ROCM_METADATA_ENFORCE``: Affects kernels written in GCN assembly + language. + + * ``0`` (or unset): Automatically detects the required CO version and assembles to that version. This is + the default. + * ``1``: Do not auto-detect CO version; always assemble v2 COs. + * ``2``: Behave as if both v2 and v3 COs are supported (see + `MIOPEN_DEBUG_AMD_ROCM_METADATA_PREFER_OLDER`). + * ``3``: Always assemble v3 COs. + +* ``MIOPEN_DEBUG_AMD_ROCM_METADATA_PREFER_OLDER``: This variable only affects assembly + kernels, and only when ROCm supports both v2 and v3 COs (like ROCm 2.10). By default, the newer + format is used (v3 CO). When this variable is enabled, the behavior is reversed. +* ``MIOPEN_DEBUG_OPENCL_ENFORCE_CODE_OBJECT_VERSION``: Enforces CO format for OpenCL + kernels. This only works with the HIP backend (``cmake ... -DMIOPEN_BACKEND=HIP...``). + + * Unset - Automatically detect the required CO version. This is the default. + * ``2``: Always build to v2 CO. + * ``3``: Always build to v3 CO. + * ``4``: Always build to v4 CO. + +Winograd multi-pass maximum workspace throttling +------------------------------------------------------------------------------------------------------------- + +* ``MIOPEN_DEBUG_AMD_WINOGRAD_MPASS_WORKSPACE_MAX`` -- + ``ConvWinograd3x3MultipassWrW``, WrW +* ``MIOPEN_DEBUG_AMD_MP_BD_WINOGRAD_WORKSPACE_MAX`` -- ``ConvMPBidirectWinograd*``, + FWD BWD + +Syntax of value: + +* Decimal or hex (with ``0x`` prefix) value that must fit into a 64-bit unsigned integer +* If the syntax is violated, then the behavior is unspecified + +Semantics: + +* Sets the limit (max allowed workspace size) for multi-pass (MP) Winograd solutions, in bytes. +* Affects all MP Winograd solutions. If a solution needs more workspace than the limit, it doesn't apply. +* If unset, then the default limit is used. The current default is ``2000000000`` (~1.862 GiB) for gfx900 + and gfx906/60 (or less CUs). No default limit is set for other GPUs. +* Special values: + + * ``0``: Use the default limit, as if the variable is unset + * ``1``: Completely prohibit the use of workspace + * ``-1``: Remove the default limit diff --git a/docs/how-to/find-and-immediate.rst b/docs/how-to/find-and-immediate.rst new file mode 100644 index 0000000000..d94eb01a55 --- /dev/null +++ b/docs/how-to/find-and-immediate.rst @@ -0,0 +1,240 @@ +.. meta:: + :description: Find and immediate modes + :keywords: MIOpen, ROCm, API, documentation + +*********************************************************************************** +Using the find APIs and immediate mode +*********************************************************************************** + +MIOpen contains several convolution algorithms for each stage of training or inference. Prior to +MIOpen version 2.0, you had to call find methods in order generate a set of applicable algorithms. + +Here's a typical workflow for the find stage: + +.. code:: cpp + + miopenConvolutionForwardGetWorkSpaceSize(handle, + weightTensorDesc, + inputTensorDesc, + convDesc, + outputTensorDesc, + &maxWorkSpaceSize); + + // < allocate workspace > + + + // NOTE: + // The miopenFindConvolution*() call is expensive in terms of run time and required workspace. + // Therefore, we highly recommend reserving the required algorithm and workspace so that you can + // reuse them later (within the lifetime of the same MIOpen handle object). + // With this approach, there should be no need to invoke miopenFind*() more than once per + // application lifetime. + + miopenFindConvolutionForwardAlgorithm(handle, + inputTensorDesc, + input_device_mem, + weightTensorDesc, + weight_device_mem, + convDesc, + outputTensorDesc, + output_device_mem,, + request_algo_count, + &ret_algo_count, + perf_results, + workspace_device_mem, + maxWorkSpaceSize, + 1); + + // < select fastest algorithm > + + // < free previously allocated workspace and allocate workspace required for the selected algorithm> + + miopenConvolutionForward(handle, &alpha, + inputTensorDesc, + input_device_mem, + weightTensorDesc, + weight_device_mem, + convDesc, + perf_results[0].fwd_algo, // use the fastest algo + &beta, + outputTensorDesc, + output_device_mem, + workspace_device_mem, + perf_results[0].memory); //workspace size + +The results of `find` are returned in an array of ``miopenConvAlgoPerf_t`` structs in order of +performance, with the fastest at index 0. + +This call sequence is only run once per session, as it's inherently expensive. Within the sequence, +``miopenFindConvolution*()`` is the most expensive call. ``miopenFindConvolution*()`` caches its own +results on disk so the subsequent calls during the same MIOpen session run faster. + +Internally, MIOpen's find calls compile and benchmark a set of ``solvers`` contained in +``miopenConvAlgoPerf_t``. This is performed in parallel with ``miopenConvAlgorithm_t``. You can +control the level of parallelism using an environmental variable. Refer to the debugging section, +:ref:`controlling parallel compilation ` for more information. + +Immediate mode +===================================================== + +MIOpen v2.0 introduces immediate more, which removes the requirement for +``miopenFindConvolution*()`` calls, thereby reducing runtime costs. In this mode, you can query the +MIOpen runtime for all of the supported solutions for a given convolution configuration. The sequence +of operations for immediate mode is similar to launching regular convolutions in MIOpen (i.e., through +the use of the ``miopenFindConvolution*()`` API). However, in this case, the different APIs have a lower +runtime cost. + +A typical convolution call is similar to the following sequence: + +* You construct the MIOpen handle and relevant descriptors, such as the convolution descriptor. +* With the above data structures, you call ``miopenConvolution*GetSolutionCount`` to get the + maximum number of supported solutions for the convolution descriptor. +* The obtained count is used to allocate memory for the ``miopenConvSolution_t`` structure, + introduced in MIOpen v2.0. +* You call ``miopenConvolution*GetSolution`` to populate the ``miopenConvSolution_t`` structures + allocated above. The returned list is in the order of best performance (where the first element is the + fastest). +* While the above structure returns the amount of workspace required for an algorithm, you can + inquire the amount of a workspace required for a known solution ID using + ``miopenConvolution*GetSolutionWorkspaceSize``. However, this is not a requirement (because the + structure returned by ``miopenConvolution*GetSolution`` already has this information). +* Now you can initiate the convolution operation in ``immediate`` mode by calling + ``miopenConvolution*Immediate``. This populates the output tensor descriptor with the respective + convolution result. However, the first call to ``miopenConvolution*Immediate`` may consume more + time because if the kernel isn't present in the kernel cache, it would need to be compiled. +* Optionally, you can compile the solution of choice by calling ``miopenConvolution*CompileSolution``. + This ensures that the kernel represented by the chosen solution is populated in the kernel cache, + removing the need to compile the kernel in question. + +.. code:: cpp + + miopenConvolutionForwardGetSolutionCount(handle, + weightTensorDesc, + inputTensorDesc, + convDesc, + outputTensorDesc, + &solutionCount); + + + // < allocate an array of miopenConvSolution_t of size solutionCount > + + + miopenConvolutionForwardGetSolution(handle, + weightTensorDesc, + inputTensorDesc, + convDesc, + outputTensorDesc, + solutionCount, + &actualCount, + solutions); + + // < select a solution from solutions array > + + miopenConvolutionForwardGetSolutionWorkspaceSize(handle, + weightTensorDesc, + inputTensorDesc, + convDesc, + outputTensorDesc, + selected->solution_id, + &ws_size); + + // < allocate solution workspace of size ws_size > + + + // This stage is optional. + miopenConvolutionForwardCompileSolution(handle, + weightTensorDesc, + inputTensorDesc, + convDesc, + outputTensorDesc, + selected->solution_id); + + + + miopenConvolutionForwardImmediate(handle, + weightTensor, + weight_device_mem, + inputTensorDesc, + input_device_mem, + convDesc, + outputTensorDesc, + output_device_mem, + workspace_device_mem, + ws_size, + selected->solution_id); + +Immediate mode fallback +----------------------------------------------------------------------------------------------- + +Although immediate mode is underpinned by :doc:`FindDb <../conceptual/finddb>`, it may not contain every +configuration of interest. If FindDb encounters a database miss, it has two fallback paths it can take, +depending on whether the CMake variable ``MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK`` is set to +``ON`` or ``OFF``. + +If you require the best possible performance, run the find stage at least once. + +AI-based heuristic fallback (default) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If ``MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK`` is set to ``ON`` (default), the immediate mode +behavior upon encountering a database miss is to use an AI-based heuristic to pick the optimal +solution. + +First, the applicability of the AI-based heuristic for the given configuration is checked. If the heuristic is +applicable, it feeds various parameters of the given configuration into a neural network that has been +tuned to predict the optimal solution with 90% accuracy. + +Weighted throughput index-based fallback +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When ``MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK`` is set to ``OFF``, or the AI heuristic is not +applicable for the given convolution configuration, the immediate mode behavior upon encountering +a database miss is to use a weighted throughput index-based mechanism to estimate which solution +would be optimal (based on the convolution configuration parameters). + +Limitations of immediate mode +----------------------------------------------------------------------------------------------- + +System FindDb has only been populated for these architectures: + +* gfx906 with 64 CUs +* gfx906 with 60 CUs +* gfx900 with 64 CUs +* gfx900 with 56 CUs + +If your architecture isn't listed, you must run the find API on your system (once per application) in +order to take advantage of immediate mode's more efficient behavior. + +Backend limitations +----------------------------------------------------------------------------------------------- + +OpenCL support for immediate mode via the fallback is limited to FP32 datatypes. This is because the +current release's fallback path goes through GEMM, which is serviced through MIOpenGEMM (on +OpenCL). MIOpenGEMM only contains support for FP32. + +The HIP backend uses rocBLAS as its fallback path, which contains a more robust set of datatypes. + + +Find modes +============================================================ + +MIOpen provides a set of find modes that are used to accelerate find API calls. The different +modes are set by using the ``MIOPEN_FIND_MODE`` environment variable with one of these values: + +* ``NORMAL``/``1`` (normal find): This is the full find mode call, which benchmarks all the solvers and + returns a list. +* ``FAST``/``2`` (fast find): Checks :doc:`FindDb <../conceptual/finddb>` for an entry. If there's a FindDb + hit, it uses that entry. If there's a miss, it uses the immediate mode fallback. Offers fast start-up times + at the cost of GPU performance. +* ``HYBRID``/``3`` or unset ``MIOPEN_FIND_MODE`` (hybrid find): Checks + :doc:`FindDb <../conceptual/finddb>` for an entry. If there's a FindDb hit, it uses that entry. If there's a + miss, it uses the existing find machinery. Offers slower start-up times than fast find without the GPU + performance drop. +* ``4``: This value is reserved and should not be used. +* ``DYNAMIC_HYBRID``/``5`` (dynamic hybrid find): Checks :doc:`FindDb <../conceptual/finddb>` for an + entry. If there's a FindDb hit, it uses that entry. If there's a miss, it uses the existing find machinery + (skipping non-dynamic kernels). It offers faster start-up times than hybrid find, but GPU performance + may decrease. + +The default find mode is ``DYNAMIC_HYBRID``. To run the full ``NORMAL`` find mode, use +``export MIOPEN_FIND_MODE=NORMAL`` or ``export MIOPEN_FIND_MODE=1``. diff --git a/docs/how-to/use-fusion-api.rst b/docs/how-to/use-fusion-api.rst new file mode 100644 index 0000000000..dfc6751edc --- /dev/null +++ b/docs/how-to/use-fusion-api.rst @@ -0,0 +1,305 @@ +.. meta:: + :description: Using the Fusion API + :keywords: MIOpen, ROCm, API, documentation, fusion API + +************************************************************************************************ +Using the fusion API +************************************************************************************************ + +Increasing the depth of deep-learning networks requires novel mechanisms to improve GPU +performance. One mechanism to achieve higher efficiency is to `fuse` separate kernels into a single +kernel in order to reduce off-chip memory access and avoid kernel launch overhead. + +Using MIOpen's fusion API, you can specify operators that you want to fuse into a single kernel, +compile that kernel, and then launch it. While not all combinations are supported, the API is flexible +enough to allow the specification of several operations, in any order, from the set of supported +operations. The API provides a mechanism to report unsupported combinations. + +You can find a complete example of MIOpen's fusion API in our GitHub repository +`example folder `_. The code +examples in this document are from the example project. + +.. note:: + The example project creates a fusion plan to merge the convolution, bias, and activation operations. + For a list of supported fusion operations and associated constraints, refer to the + :ref:`Supported fusions ` section. For simplicity, the example doesn't populate + the tensors with meaningful data and shows only basic code without any error checking. + +Once you've initialized an MIOpen handle object, the workflow for using the fusion API is: + +* Create a fusion plan +* Create and add the convolution, bias, and activation operators +* Compile the fusion plan +* Set the runtime arguments for each operator +* Run the fusion plan +* Cleanup + +The order in which you create operators is important because this order represents the order of +operations for the data. Therefore, a fusion plan where convolution is created before activation differs +from a fusion plan where activation is added before convolution. + +.. note:: + The primary consumers of the fusion API are high-level frameworks, such as TensorFlow/XLA and + PyTorch. + +Creating a fusion plan +================================================= + +A **fusion plan** is the data structure that holds all the metadata regarding fusion intent, and the +logic to compile and run a fusion plan. The fusion plan not only contains the order in which different +operations are applied on the data, but also specifies the `axis` of fusion. Currently, only `vertical` +(sequential) fusions are supported (implying the flow of data between operations is sequential). + +You can create a fusion plan using ``miopenCreateFusionPlan``, as follows: + +.. code:: cpp + + miopenStatus_t + miopenCreateFusionPlan(miopenFusionPlanDescriptor_t* fusePlanDesc, + const miopenFusionDirection_t fuseDirection,const miopenTensorDescriptor_t inputDesc); + +The `input tensor descriptor` specifies the geometry of the incoming data. Because the data geometry +of the intermediate operations can be derived from the input tensor descriptor, only this is required for +the fusion plan (i.e. the input tensor descriptor isn't required for the individual operations). + +.. code:: cpp + + miopenCreateFusionPlan(&fusePlanDesc, miopenVerticalFusion, input.desc); + +Where: +* ``fusePlanDesc`` is an object of type ``miopenFusionPlanDescriptor_t`` +* ``input.desc`` is the ``miopenTensorDescriptor_t`` object + +Creating and adding operators +================================================= + +Operators represent the different operations that you want fused. Currently, the API supports these +operators: + +* Convolution forward +* Activation forward +* BatchNorm inference +* Bias forward + +.. note:: + + Although bias is a separate operator, it's typically only used with convolution. + +We plan to add support for more operators, including operators for backward passes, in the future. + +The fusion API provides calls for the creation of the supported operators. To learn more, refer to the +:doc:`Fusion <../doxygen/html/group___f_u_s_i_o_n>` API documentation. + +Once you've created the fusion plan descriptor, you can add two or more operators to it by using the +individual operator creation API calls. If the API doesn't support the fusion of the operations you add, +the creation might fail. + +In our example, we add the convolution, bias, and activation operations to our newly created fusion +plan. + +.. code:: cpp + + miopenStatus_t + miopenCreateOpConvForward(miopenFusionPlanDescriptor_t fusePlanDesc, + miopenFusionOpDescriptor_t* convOp, + miopenConvolutionDescriptor_t convDesc, + const miopenTensorDescriptor_t wDesc); + miopenStatus_t + miopenCreateOpBiasForward(miopenFusionPlanDescriptor_t fusePlanDesc, + miopenFusionOpDescriptor_t* biasOp, + const miopenTensorDescriptor_t bDesc); + + miopenStatus_t + miopenCreateOpActivationForward(miopenFusionPlanDescriptor_t fusePlanDesc, + miopenFusionOpDescriptor_t* activOp, + miopenActivationMode_t mode); + + +``conv_desc`` is the regular MIOpen convolution descriptor. For more information on creating and +setting the this descriptor, refer to the example code and the +:doc:`Convolution <../doxygen/html/group__convolutions>` API documentation. + +``weights.desc`` refers to ``miopenTensorDescriptor_t`` for the convolution operations. + +``bias.desc`` refers to the object of the same type for the bias operation. + +In the preceding code, the convolution operation is the first operation to run on the incoming data, +followed by the bias, and then activation operations. + +During this process, it is important that you verify the returned codes to make sure the operations (and +their order) is supported. The operator insertion can fail for a number of reasons, such as unsupported +operation sequence, unsupported input dimensions, or, in the case of convolution, unsupported filter +dimensions. In the preceding example, these aspects are ignored for the sake of simplicity. + +Compiling the fusion plan +================================================= + +Following the operator addition, you can compile the fusion plan. This populates the MIOpen kernel +cache with the fused kernel and gets it ready to run. + +.. code:: cpp + + miopenStatus_t + miopenCompileFusionPlan(miopenHandle_t handle, miopenFusionPlanDescriptor_t fusePlanDesc); + + +The corresponding code snippet in the example is: + +.. code:: cpp + + auto status = miopenCompileFusionPlan(mio::handle(), fusePlanDesc); + if (status != miopenStatusSuccess) { + return -1; + } + +In order to compile the fusion plan, you must have acquired an MIOpen handle object. In the +preceding code, this is accomplished using the ``mio::handle()`` helper function. While a fusion plan is +itself not bound to an MIOpen handle object, it must be recompiled for each handle separately. + +Compiling a fusion plan is a costly operation in terms of run-time, and compilation can fail for a +number of reasons. Therefore, we recommended only compiling your fusion plan once and reusing it +with different runtime parameters, as described in the next section. + +Setting runtime arguments +================================================= + +While the fusion operator for the underlying MIOpen descriptor specifies the data geometry and +parameters, the fusion plan still needs access to the data to run a successfully compiled fusion plan. +The arguments mechanism in the fusion API provides this data before a fusion plan can be run. For +example, the convolution operator requires `weights` to carry out the convolution computation,and the +bias operator requires the actual bias values. Therefore, before you can run a fusion plan, you must +specify the arguments required by each fusion operator. + +To begin, create the ``miopenOperatorArgs_t`` object using: + +.. code:: cpp + + miopenStatus_t miopenCreateOperatorArgs(miopenOperatorArgs_t* args); + +Once created, you can set the runtime arguments for each operation. In our example, the forward +convolution operator requires the convolution weights argument, which is supplied using: + +.. code:: cpp + + miopenStatus_t + miopenSetOpArgsConvForward(miopenOperatorArgs_t args, + const miopenFusionOpDescriptor_t convOp, + const void* alpha, + const void* beta, + const void* w); + +Similarly, the parameters for bias and activation are given by: + +.. code:: cpp + + miopenStatus_t miopenSetOpArgsBiasForward(miopenOperatorArgs_t args, + const miopenFusionOpDescriptor_t biasOp, + const void* alpha, + const void* beta, + const void* bias); + + miopenStatus_t miopenSetOpArgsActivForward(miopenOperatorArgs_t args, + const miopenFusionOpDescriptor_t activOp, + const void* alpha, + const void* beta, + double activAlpha, + double activBeta, + double activGamma); + +In our example code, we set the arguments for the operations as follows: + +.. code:: cpp + + miopenSetOpArgsConvForward(fusionArgs, convoOp, &alpha, &beta, weights.data); + miopenSetOpArgsActivForward(fusionArgs, activOp, &alpha, &beta, activ_alpha, + activ_beta, activ_gamma); + miopenSetOpArgsBiasForward(fusionArgs, biasOp, &alpha, &beta, bias.data); + +The separation between the fusion plan and the arguments required by each operator allows better +reuse of the fusion plan with different arguments. It also avoids the necessity to recompile the fusion +plan to run the same combination of operators with different arguments. + +As previously mentioned, the compilation step for a fusion plan can be costly; therefore, we +recommend only compiling a fusion plan once in its lifetime. A fusion plan doesn't need to be +recompiled if the input descriptor or any of the parameters in the ``miopenCreateOp*`` API calls are +different. You can repeatedly reuse a compiled fusion plan with a different set of arguments. + +In our example, this is demonstrated in ``main.cpp``, lines 77 through 85. + +Running a fusion plan +======================================================== + +Once you've compiled the fusion plan and set arguments set for each operator, you can run it as +follows: + +.. code:: cpp + + miopenStatus_t + miopenExecuteFusionPlan(const miopenHandle_t handle, + const miopenFusionPlanDescriptor_t fusePlanDesc, + const miopenTensorDescriptor_t inputDesc, + const void* input, + const miopenTensorDescriptor_t outputDesc, + void* output, + miopenOperatorArgs_t args); + +The following code snippet runs the fusion plan: + +.. code:: cpp + + miopenExecuteFusionPlan(mio::handle(), fusePlanDesc, input.desc, input.data, + output.desc, output.data, fusionArgs); + +If you try to run a fusion plan that is not compiled, or has been invalidated by changing the input +tensor descriptor or any of the operation parameters, you'll get an error. + +Cleanup +================================================= + +Once the application is done with the fusion plan, you can detroy the fusion plan and the fusion args +objects: + +.. code:: cpp + + miopenStatus_t miopenDestroyFusionPlan(miopenFusionPlanDescriptor_t fusePlanDesc); + +After the fusion plan object is destroyed, all the operations are automatically destroyed, and you don't +need to worry about additional cleanup. + +.. _supported-fusions: + +Supported fusions +================================================= + +The following tables outline the supported fusions for FP32 and FP16, including any applicable +constraints. + +.. note:: + + Fusion Plans with grouped convolutions are not supported. + +**C = convolution, B = bias, N = batch normalization, A = activation** + +.. image:: ../data/how-to/fp32fusions.png + :width: 800 + :alt: Convolution based fp32 fusion + +.. image:: ../data/how-to/fp16fusions.png + :width: 800 + :alt: Convolution based fp16 fusion + +Comparing performance with non-fused kernels +================================================= + +The following graph depicts the speedup gained for a fused convolution+bias+activation over a +non-fused version. All configurations have a batch size of 64: + +.. image:: ../data/how-to/cba.png + :width: 800 + :alt: convolution-bias-activation graph + +The following graph depicts the speedup obtained by fusing BatchNorm (spatial mode) with activation: + +.. image:: ../data/how-to/na.png + :width: 800 + :alt: BatchNorm activation fusion diff --git a/docs/index.rst b/docs/index.rst index a9687765e6..d079248229 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,16 +1,48 @@ -.. miopen documentation master file, created by - sphinx-quickstart on Thu Jun 15 12:09:40 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +.. meta:: + :description: MIOpen documentation + :keywords: MIOpen, ROCm, API, documentation -Welcome to MIOpen -================= -**Advanced Micro Devices's open source deep learning library.** +******************************************************************** +MIOpen documentation +******************************************************************** -Sources and binaries can be found at `MIOpen's GitHub site `_. +Welcome to our documentation home page. To learn more about MIOpen, refer to +:doc:`What is MIOpen? <./what-is-miopen>` -Indices and tables -================== +Our documentation is structured as follows: -* :ref:`genindex` -* :ref:`search` +.. grid:: 2 + :gutter: 3 + + .. grid-item-card:: Install + + * :doc:`Install MIOpen <./install/install>` + * :doc:`Build MIOpen for embedded systems <./install/embed>` + * :doc:`Build MIOpen using Docker <./install/docker-build>` + + .. grid-item-card:: Reference + + * :doc:`API library ` + + * :doc:`Modules <./doxygen/html/modules>` + * :doc:`Datatypes ` + + .. grid-item-card:: Conceptual + + * :doc:`MI200 alternate implementation <./conceptual/MI200-alt-implementation>` + * :doc:`Cache <./conceptual/cache>` + * :doc:`Find database <./conceptual/finddb>` + * :doc:`Performance database <./conceptual/perfdb>` + * :doc:`Porting to MIOpen <./conceptual/porting-guide>` + + .. grid-item-card:: How to + + * :doc:`Use fusion <./how-to/use-fusion-api>` + * :doc:`Log & debug <./how-to/debug-log>` + * :doc:`Use the find APIs & immediate mode <./how-to/find-and-immediate>` + +To contribute to the documentation refer to +`Contributing to ROCm `_. + +You can find licensing information for all ROCm components on the +`ROCm licensing `_ page. diff --git a/docs/install.md b/docs/install.md deleted file mode 100644 index 1b0938d193..0000000000 --- a/docs/install.md +++ /dev/null @@ -1,71 +0,0 @@ -## Prerequisites - -* More information about ROCm stack via [ROCm Information Portal](https://docs.amd.com/). -* A ROCm enabled platform, more info [here](https://rocmdocs.amd.com/en/latest/). -* Base software stack, which includes: - * HIP - - * HIP and HCC libraries and header files. - * OpenCL - OpenCL libraries and header files. -* [ROCm cmake](https://github.com/RadeonOpenCompute/rocm-cmake) - provide cmake modules for common build tasks needed for the ROCM software stack. -* [Half](http://half.sourceforge.net/) - IEEE 754-based half-precision floating point library -* [Boost](http://www.boost.org/) - * MIOpen uses `boost-system` and `boost-filesystem` packages - * Version 1.83 is recommended -* [SQLite3](https://sqlite.org/index.html) - reading and writing performance database, enabling persistent [kernel cache](https://rocm.docs.amd.com/projects/MIOpen/en/latest/cache.html) -* [rocBLAS](https://github.com/ROCm/rocBLAS) - AMD library for Basic Linear Algebra Subprograms (BLAS) on the ROCm platform. - * Minimum version branch for pre-ROCm 3.5 [master-rocm-2.10](https://github.com/ROCm/rocBLAS/tree/master-rocm-2.10) - * Minimum version branch for post-ROCm 3.5 [master-rocm-3.5](https://github.com/ROCm/rocBLAS/tree/master-rocm-3.5) -* [MLIR](https://github.com/ROCm/rocMLIR) - (Multi-Level Intermediate Representation) with its MIOpen dialect to support and complement kernel development. -* [Composable Kernel](https://github.com/ROCm/composable_kernel) - C++ templated device library for GEMM-like and reduction-like operators. - -## Installing MIOpen with pre-built packages - -MIOpen can be installed on Ubuntu using `apt-get`. - -For OpenCL backend: `apt-get install miopen-opencl` - -For HIP backend: `apt-get install miopen-hip` - -Currently both the backends cannot be installed on the same system simultaneously. If a different backend other than what currently exists on the system is desired, please uninstall the existing backend completely and then install the new backend. - -## Installing MIOpen kernels package - -MIOpen provides an optional pre-compiled kernels package to reduce the startup latency. These precompiled kernels comprise a select set of popular input configurations and will expand in future release to contain additional coverage. - -Note that all compiled kernels are locally cached in the folder `$HOME/.cache/miopen/`, so precompiled kernels reduce the startup latency only for the first execution of a neural network. Precompiled kernels do not reduce startup time on subsequent runs. - -To install the kernels package for your GPU architecture, use the following command: - -``` -apt-get install miopenkernels-- -``` - -Where `` is the GPU architecture ( for example, `gfx900`, `gfx906`, `gfx1030` ) and `` is the number of CUs available in the GPU (for example 56 or 64 etc). - -Not installing these packages would not impact the functioning of MIOpen, since MIOpen will compile these kernels on the target machine once the kernel is run. However, the compilation step may significantly increase the startup time for different operations. - -The script `utils/install_precompiled_kernels.sh` provided as part of MIOpen automates the above process, it queries the user machine for the GPU architecture and then installs the appropriate package. It may be invoked as: - -``` -./utils/install_precompiled_kernels.sh -``` - -The above script depends on the __rocminfo__ package to query the GPU architecture. - -More info can be found [here](https://github.com/ROCm/MIOpen/blob/develop/docs/cache.md#installing-pre-compiled-kernels). - -## Installing the dependencies - -The dependencies can be installed with the `install_deps.cmake`, script: `cmake -P install_deps.cmake` - -This will install by default to `/usr/local` but it can be installed in another location with `--prefix` argument: -``` -cmake -P install_deps.cmake --prefix -``` -An example cmake step can be: -``` -cmake -P install_deps.cmake --minimum --prefix /root/MIOpen/install_dir -``` -This prefix can used to specify the dependency path during the configuration phase using the `CMAKE_PREFIX_PATH`. - -* MIOpen's HIP backend uses [rocBLAS](https://github.com/ROCm/rocBLAS) by default. Users can install rocBLAS minimum release by using `apt-get install rocblas`. To disable using rocBLAS set the configuration flag `-DMIOPEN_USE_ROCBLAS=Off`. rocBLAS is *not* available for the OpenCL backend. diff --git a/docs/install/docker-build.rst b/docs/install/docker-build.rst new file mode 100644 index 0000000000..620bdbb797 --- /dev/null +++ b/docs/install/docker-build.rst @@ -0,0 +1,89 @@ +.. meta:: + :description: Build MIOpen using Docker + :keywords: MIOpen, ROCm, API, documentation + +******************************************************************** +Build MIOpen using Docker +******************************************************************** + +You can build MIOpen using Docker by either downloading a prebuilt image or creating your own. + +.. note:: + + For ease of use, we recommended using the prebuilt Docker image. + +* Downloading a prebuilt image + + You can find prebuilt Docker images at + `ROCm Docker Hub `_. + +* Building your own image + + .. code-block:: bash + + docker build -t miopen-image . + + To enter the development environment, use ``docker run``. For example: + + .. code-block:: bash + + docker run -it -v $HOME:/data --privileged --rm --device=/dev/kfd --device /dev/dri:/dev/dri:rw + --volume /dev/dri:/dev/dri:rw -v /var/lib/docker/:/var/lib/docker --group-add video + --cap-add=SYS_PTRACE --security-opt seccomp=unconfined miopen-image + + Once in the Docker environment, use ``git clone MIOpen``. You can now start building MIOpen using + CMake. + +Building MIOpen from source +========================================================== + +Use the following instructions to build MIOpen from source. + +1. Create a build directory: + + .. code-block:: bash + + mkdir build; cd build; + +2. Configure CMake using either an MIOpen or a HIP backend. + + **MIOpen backend**: + + Set your preferred backend using the ``-DMIOPEN_BACKEND`` CMake variable. + + **HIP backend (ROCm 3.5 and later)**: + + First, set the C++ compiler to ``clang++``: + + .. code-block:: bash + + export CXX= + + Then, run the following command to configure CMake: + + .. code-block:: bash + + cmake -DMIOPEN_BACKEND=HIP -DCMAKE_PREFIX_PATH=";;" .. + + For example, you can set CMake to: + + .. code-block:: bash + + export CXX=/opt/rocm/llvm/bin/clang++ && \ + cmake -DMIOPEN_BACKEND=HIP -DCMAKE_PREFIX_PATH="/opt/rocm/;/opt/rocm/hip;/root/MIOpen/install_dir" .. + + .. note:: + + When specifying the path for ``CMAKE_PREFIX_PATH``, **do not** use the tilde (~) shorthand + for the home directory. + + +Choosing an install location +------------------------------------------------------------------------------------- + +By default, the install location is set to ``/opt/rocm``. If you used a different install location, set your +install path using ``CMAKE_INSTALL_PREFIX``: + +.. code-block:: bash + + cmake -DMIOPEN_BACKEND=OpenCL -DCMAKE_INSTALL_PREFIX= .. diff --git a/docs/install/embed.rst b/docs/install/embed.rst new file mode 100644 index 0000000000..1567027887 --- /dev/null +++ b/docs/install/embed.rst @@ -0,0 +1,121 @@ +.. meta:: + :description: Build MIOpen for embedded systems + :keywords: MIOpen, ROCm, API, documentation + +******************************************************************** +Build MIOpen for embedded systems +******************************************************************** + +1. Install dependencies. The default location is ``/usr/local``: + + .. code:: cpp + + cmake -P install_deps.cmake --minimum --prefix /some/local/dir + +2. Create the build directory. + + .. code:: cpp + + mkdir build; cd build; + +3. Configure for an embedded build. + + The minimum static build configuration line, without an embedded precompiled kernels package or + FindDb is: + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BACKEND=HIP -DMIOPEN_EMBED_BUILD=On -DCMAKE_PREFIX_PATH="/some/local/dir" .. + + + To enable HIP kernels in MIOpen while using embedded builds, add + ``-DMIOPEN_USE_HIP_KERNELS=On`` to configure line. For example: + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BACKEND=HIP -DMIOPEN_USE_HIP_KERNELS=On -DMIOPEN_EMBED_BUILD=On -DCMAKE_PREFIX_PATH="/some/local/dir" .. + +4. Embed FindDb and PerfDb. + + FindDb provides a database of known convolution inputs. This allows you to use the best tuned + kernels for your network. Embedding FindDb requires a semicolon-separated list of architecture CU + pairs to embed on-disk databases in the binary (e.g., ``gfx906_60;gfx900_56``). + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_EMBED_BUILD=On -DMIOPEN_EMBED_DB=gfx900_56 .. + + This configures the build directory for embedding (FindDb and PerfDb). + +5. Embed the precompiled kernels package. + + To prevent the loss of performance due to compile-time overhead, an MIOpen build can embed the + precompiled kernels package. This package contains convolution kernels of known inputs, and allows + you to avoid compiling kernels during runtime. + + * Embed the precompiled package using a package install. + + .. code:: bash + + apt-get install miopenkernels-- + + Where ```` is the GPU architecture (e.g., gfx900, gfx906) and ```` is the number of + compute units (CUs) available in the GPU (e.g., 56, 64). + + If you choose not to install the precompiled kernel package, there is no impact to the functioning of + MIOpen because MIOpen compiles these kernels on the target machine once the kernel is run. + However, the compilation step may significantly increase the startup time for different operations. + + The ``utils/install_precompiled_kernels.sh`` script automates the above process. It queries your + machine for the GPU architecture and then installs the appropriate package. You can invoke it using: + + .. code:: cpp + + ./utils/install_precompiled_kernels.sh + + To embed the precompiled kernels package, configure CMake using the + ``MIOPEN_BINCACHE_PATH``. + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/path/to/package/install -DMIOPEN_EMBED_BUILD=On .. + + * Using the URL to a kernels binary. You can use the ``MIOPEN_BINCACHE_PATH`` flag with a URL that + contains the binary. + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/URL/to/binary -DMIOPEN_EMBED_BUILD=On .. + + Precompiled kernels packages are installed in ``/opt/rocm/miopen/share/miopen/db``. + + Here's an example with gfx900 architecture and 56 CUs: + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/opt/rocm/miopen/share/miopen/db/gfx900_56.kdb -DMIOPEN_EMBED_BUILD=On .. + + As of ROCm 3.8 and MIOpen 2.7, precompiled kernels binaries are located at + `repo.radeon.com `_. + + Here's an example with gfx906 architecture and 64 CUs: + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=http://repo.radeon.com/rocm/miopen-kernel/rel-3.8/gfx906_60.kdb -DMIOPEN_EMBED_BUILD=On .. + + +6. Full configuration line. + + To build MIOpen statically and embed the performance database, FindDb, and the precompiled + kernels binary: + + .. code:: cpp + + CXX=/opt/rocm/llvm/bin/clang++ cmake -DMIOPEN_BINCACHE_PATH=/path/to/package/install -DMIOPEN_EMBED_BUILD=On -DMIOPEN_EMBED_DB=gfx900_56 .. + + After configuration is complete, run: + + .. code:: cpp + + make -j diff --git a/docs/install/install.rst b/docs/install/install.rst new file mode 100644 index 0000000000..ab574b9bca --- /dev/null +++ b/docs/install/install.rst @@ -0,0 +1,348 @@ +.. meta:: + :description: Installing MIOpen + :keywords: MIOpen, ROCm, API, documentation + +******************************************************************** +Installing MIOpen +******************************************************************** + +To install MIOpen, you must first install these prerequisites: + +* A :doc:`ROCm `-enabled platform +* A base software stack that includes either: + + * HIP (HIP and HCC libraries and header files) + * OpenCL (OpenCL libraries and header files)--this is now deprecated + +* `ROCm CMake `_: provides CMake modules for common + build tasks needed for the ROCm software stack +* `Half `_: IEEE 754-based, half-precision floating-point library +* `Boost `_: Version 1.79 is recommended, as older versions may need patches + to work on newer systems + + * MIOpen uses ``boost-system`` and ``boost-filesystem`` packages to enable persistent + :doc:`kernel cache <../conceptual/cache>` + +* `SQLite3 `_: A reading and writing performance database +* lbzip2: A multi-threaded compress or decompress utility +* :doc:`rocBLAS `: AMD's library for Basic Linear Algebra Subprograms (BLAS) on the + ROCm platform. + + * Minimum version branch for pre-ROCm 3.5 + `master-rocm-2.10 `_ + * Minimum version branch for post-ROCm 3.5 + `master-rocm-3.5 `_ + +* `Multi-Level Intermediate Representation (MLIR) `_ with its + MIOpen dialect to support and complement kernel development +* :doc:`Composable Kernel `: A C++ templated device library for + GEMM-like and reduction-like operators. + +Installing with pre-built packages +============================================================== + +You can install MIOpen on Ubuntu using ``apt-get install miopen-hip``. + +If using OpenCL, you can use ``apt-get install miopen-opencl`` (but this is not recommended, as +OpenCL is deprecated). + +Note that you can't install both backends on the same system simultaneously. If you want a different +backend other than what currently exists, completely uninstall the existing backend prior to installing +the new backend. + +Installing with a kernels package +-------------------------------------------------------------------------------------------------------- + +MIOpen provides an optional pre-compiled kernels package to reduce startup latency. These +precompiled kernels comprise a select set of popular input configurations. We'll expand these kernels +in future releases to include additional coverage. + +Note that all compiled kernels are locally cached in the ``$HOME/.cache/miopen/`` folder, so +precompiled kernels reduce the startup latency only for the first run of a neural network. Precompiled +kernels don't reduce startup time on subsequent runs. + +To install the kernels package for your GPU architecture, use the following command: + +.. code:: shell + + apt-get install miopenkernels-- + +Where ```` is the GPU architecture (e.g., ``gfx900``, ``gfx906``, ``gfx1030`` ) and ```` is the +number of CUs available in the GPU (e.g., ``56``, ``64``). + +.. note:: + + Not installing these packages doesn't impact the functioning of MIOpen, since MIOpen compiles + them on the target machine once you run the kernel. However, the compilation step may significantly + increase the startup time for different operations. + +The ``utils/install_precompiled_kernels.sh`` script provided as part of MIOpen automates the preceding +process. It queries the user machine for the GPU architecture and then installs the appropriate +package. You can invoke it using: + +.. code:: shell + + ./utils/install_precompiled_kernels.sh + +The preceding script depends on the ``rocminfo`` package to query the GPU architecture. + +Installing dependencies +-------------------------------------------------------------------------------------------------------- + +You can install dependencies using the ``install_deps.cmake`` script (``cmake -P install_deps.cmake``). + +By default, this installs to ``/usr/local``, but you can specify another location using the ``--prefix`` +argument: + +.. code:: shell + + cmake -P install_deps.cmake --prefix + +An example CMake step is: + +.. code:: shell + + cmake -P install_deps.cmake --minimum --prefix /root/MIOpen/install_dir + +You can use this prefix to specify the dependency path during the configuration phase using +``CMAKE_PREFIX_PATH``. + +MIOpen's HIP backend uses :doc:`rocBLAS ` by default. You can install rocBLAS' +minimum release using ``apt-get install rocblas``. To disable rocBLAS, set the configuration flag +``-DMIOPEN_USE_ROCBLAS=Off``. rocBLAS is **not** available with OpenCL. + +Building MIOpen from source +================================================ + +You can build MIOpen form source with a HIP backend or an OpenCL backend. + +HIP backend +-------------------------------------------------------------------------------------------------------- + +First, create a build directory: + +.. code:: shell + + mkdir build; cd build; + +Next, configure CMake. You can set the backend using the ``-DMIOPEN_BACKEND`` CMake variable. + +Set the C++ compiler to ``clang++``. For the HIP backend (ROCm 3.5 and later), run: + +.. code:: shell + + export CXX= + cmake -DMIOPEN_BACKEND=HIP -DCMAKE_PREFIX_PATH=";;" .. + +An example CMake step is: + +.. code:: shell + + export CXX=/opt/rocm/llvm/bin/clang++ && \ + cmake -DMIOPEN_BACKEND=HIP -DCMAKE_PREFIX_PATH="/opt/rocm/;/opt/rocm/hip;/root/MIOpen/install_dir" .. + +.. note:: + + When specifying the path for the `CMAKE_PREFIX_PATH` variable, **do not** use the tilde (`~`) + shorthand to represent the home directory. + +OpenCL backend +-------------------------------------------------------------------------------------------------------- + +.. note:: + + OpenCL is deprecated. We recommend using a HIP backend and following the instructions listed in + the preceding section. + +First, run: + +.. code:: shell + + cmake -DMIOPEN_BACKEND=OpenCL .. + +The preceding code assumes OpenCL is installed in one of the standard locations. If not, then manually +set these CMake variables: + +.. code:: shell + + cmake -DMIOPEN_BACKEND=OpenCL -DMIOPEN_HIP_COMPILER= -DOPENCL_LIBRARIES= -DOPENCL_INCLUDE_DIRS= .. + +Here's an example dependency path for an environment in ROCm 3.5 and later: + +.. code:: shell + + cmake -DMIOPEN_BACKEND=OpenCL -DMIOPEN_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ -DCMAKE_PREFIX_PATH="/opt/rocm/;/opt/rocm/hip;/root/MIOpen/install_dir" .. + +.. _setting-up-locations: + +Setting up locations +-------------------------------------------------------------------------------------------------------- + +By default, the install location is set to ``/opt/rocm``. You can change this using +``CMAKE_INSTALL_PREFIX``: + +.. code:: shell + + cmake -DMIOPEN_BACKEND=HIP -DCMAKE_INSTALL_PREFIX= .. + + +System performance database and user database +-------------------------------------------------------------------------------------------------------- + +The default path to the system performance database (System PerfDb) is ``miopen/share/miopen/db/`` +within the install location. The default path to the user performance database (User PerfDb) is +``~/.config/miopen/``. For development purposes, setting `BUILD_DEV` changes the default path to +both database files to the source directory: + +.. code:: shell + + cmake -DMIOPEN_BACKEND=HIP -DBUILD_DEV=On .. + + +Database paths can be explicitly customized using the ``MIOPEN_SYSTEM_DB_PATH`` (System PerfDb) +and ``MIOPEN_USER_DB_PATH`` (User PerfDb) CMake variables. + +To learn more, refer to the +:doc:`performance database <../conceptual/perfdb>` documentation. + +Persistent program cache +-------------------------------------------------------------------------------------------------------- + +By default, MIOpen caches device programs in the ``~/.cache/miopen/`` directory. Within the cache +directory, there is a directory for each version of MIOpen. You can change the location of the cache +directory during configuration using the ``-DMIOPEN_CACHE_DIR=`` flag. + +You can also disable the cache during runtime using the ``MIOPEN_DISABLE_CACHE=1`` environmental +variable. + +For MIOpen version 2.3 and earlier +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the compiler changes, or you modify the kernels, then you must delete the cache for the MIOpen +version in use (e.g., ``rm -rf ~/.cache/miopen/``). You can find more +information in the :doc:`cache <../conceptual/cache>` documentation. + +For MIOpen version 2.4 and later +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +MIOpen's kernel cache directory is versioned so that your cached kernels won't collide when upgrading +from an earlier version. + +Changing the CMake configuration +-------------------------------------------------------------------------------------------------------- + +The configuration can be changed after running CMake (using ``ccmake``): + +``ccmake ..`` **or** ``cmake-gui``: ``cmake-gui ..`` + +The ``ccmake`` program can be downloaded as a Linux package (``cmake-curses-gui``), but is not +available on Windows. + +Building the library +========================================================= + +You can build the library from the ``build`` directory using the 'Release' configuration: + +``cmake --build . --config Release`` **or** ``make`` + +You can install it using the 'install' target: + +``cmake --build . --config Release --target install`` **or** ``make install`` + +This installs the library to the ``CMAKE_INSTALL_PREFIX`` path that you specified. + +Building the driver +========================================================= + +MIOpen provides an application driver that you can use to run any layer in isolation, and measure +library performance and verification. + +You can build the driver using the ``MIOpenDriver`` target: + +``cmake --build . --config Release --target MIOpenDriver`` **or** ``make MIOpenDriver`` + +Running the tests +========================================================= + +You can run tests using the 'check' target: + +``cmake --build . --config Release --target check`` **or** ``make check`` + +To build and run a single test, use the following code: + +.. code:: shell + + cmake --build . --config Release --target test_tensor + ./bin/test_tensor + +Formatting the code +========================================================= + +All the code is formatted using `clang-format`. To format a file, use: + +.. code:: shell + + clang-format-10 -style=file -i + +To format the code per commit, you can install githooks: + +.. code:: shell + + ./.githooks/install + +Storing large file using Git Large File Storage +========================================================= + +Git Large File Storage (LFS) replaces large files, such as audio samples, videos, datasets, and graphics +with text pointers inside Git, while storing the file contents on a remote server. In MIOpen, we use Gi +LFS to store our large files, such as our kernel database files (``*.kdb``) that are normally > 0.5 GB. + +You can install Git LFS using the following code: + +.. code:: shell + + sudo apt install git-lfs + git lfs install + +In the Git repository where you want to use Git LFS, track the file type using the following code (if the +file type has already been tracked, you can skip this step): + +.. code:: shell + + git lfs track "*.file_type" + git add .gitattributes + +You can pull all or a single large file using: + +.. code:: shell + + git lfs pull --exclude= + or + git lfs pull --exclude= --include "filename" + +Update the large files and push to GitHub using: + +.. code:: shell + + git add my_large_files + git commit -m "the message" + git push + +Installing the dependencies manually +=============================================================== + +If you're using Ubuntu v16, you can install the ``Boost`` packages using: + +.. code:: shell + + sudo apt-get install libboost-dev + sudo apt-get install libboost-system-dev + sudo apt-get install libboost-filesystem-dev + +.. note:: + + By default, MIOpen attempts to build with Boost statically linked libraries. If required, you can build + with dynamically linked Boost libraries using the `-DBoost_USE_STATIC_LIBS=Off` flag during the + configuration stage. However, this is not recommended. + +You must install the ``half`` header from the `half website `_. diff --git a/docs/layernorm.rst b/docs/layernorm.rst deleted file mode 100644 index 86743637a3..0000000000 --- a/docs/layernorm.rst +++ /dev/null @@ -1,18 +0,0 @@ - -Layernorm Layer(experimental) -============================= - -The layernorm types and functions. -To enable this, define MIOPEN_BETA_API before including miopen.h. - - -miopenNormMode_t ------------------------ - -.. doxygenenum:: miopenNormMode_t - -miopenLayerNormForward ----------------------------------- - -.. doxygenfunction:: miopenLayerNormForward - diff --git a/docs/loss.rst b/docs/loss.rst deleted file mode 100644 index 16a15905bf..0000000000 --- a/docs/loss.rst +++ /dev/null @@ -1,41 +0,0 @@ - -Loss Function Layer -=================== - -The loss function layer API documentation - - -miopenCTCLossAlgo_t -------------------- - -.. doxygenenum:: miopenCTCLossAlgo_t - -miopenCreateCTCLossDescriptor ------------------------------ - -.. doxygenfunction:: miopenCreateCTCLossDescriptor - -miopenGetCTCLossDescriptor --------------------------- - -.. doxygenfunction:: miopenGetCTCLossDescriptor - -miopenDestroyCTCLossDescriptor ------------------------------- - -.. doxygenfunction:: miopenDestroyCTCLossDescriptor - -miopenSetCTCLossDescriptor --------------------------- - -.. doxygenfunction:: miopenSetCTCLossDescriptor - -miopenGetCTCLossWorkspaceSize ------------------------------ - -.. doxygenfunction:: miopenGetCTCLossWorkspaceSize - -miopenCTCLoss -------------- - -.. doxygenfunction:: miopenCTCLoss diff --git a/docs/lrn.rst b/docs/lrn.rst deleted file mode 100644 index 225c37f05b..0000000000 --- a/docs/lrn.rst +++ /dev/null @@ -1,46 +0,0 @@ - -Local Response Normalization Layer -================================== - -Local Response Normalization types and functions. - -miopenLRNMode_t ---------------- - -.. doxygenenum:: miopenLRNMode_t - -miopenCreateLRNDescriptor -------------------------- - -.. doxygenfunction:: miopenCreateLRNDescriptor - -miopenSetLRNDescriptor ----------------------- - -.. doxygenfunction:: miopenSetLRNDescriptor - -miopenGetLRNDescriptor ----------------------- - -.. doxygenfunction:: miopenGetLRNDescriptor - -miopenLRNGetWorkSpaceSize -------------------------- - -.. doxygenfunction:: miopenLRNGetWorkSpaceSize - -miopenLRNForward ----------------- - -.. doxygenfunction:: miopenLRNForward - -miopenLRNBackward ------------------ - -.. doxygenfunction:: miopenLRNBackward - -miopenDestroyLRNDescriptor --------------------------- - -.. doxygenfunction:: miopenDestroyLRNDescriptor - diff --git a/docs/perfdatabase.md b/docs/perfdatabase.md deleted file mode 100644 index 5644334b2a..0000000000 --- a/docs/perfdatabase.md +++ /dev/null @@ -1,67 +0,0 @@ -Performance Database -==================== - -Many of MIOpen kernels have parameters which affect their performance. Setting these parameters to optimal values allows reaching the best possible throughput. These optimal values depend on many things, including network configuration, GPU type, clock frequencies, ROCm version etc. Because of these dependencies and also due to enormous number of possible network configurations, it is virtually impossible to supply all values that users may need together with the library. Instead, MIOpen provides a set of pre-tuned values for the _most applicable_ network configurations, **and** also means for expanding the set of optimized values. MIOpen's performance database contains these pre-tuned parameter values as well as optimized parameters tuned by users. - -The performance database consists of two parts: -- **System Performance Database**, a system-wide storage which holds the pre-tuned values for the most applicable configurations, -- **User Performance Database**, a per-user storage which is intended to hold optimized values for arbitrary configurations. - -User PerfDb **always takes precedence** over System PerfDb. - -MIOpen also has auto-tuning functionality, which is able to find optimized kernel parameter values for a specific configuration. The auto-tune process may take a substantial amount of time, however, once the optimized values are found, they are stored in the User PerfDb. MIOpen then will automatically read and use these parameter values when needed again instead of running the expensive auto-tuning search. - -By default, System PerfDb resides within MIOpen's install location, while User PerfDb resides in the user's home directory. See [Setting up locations](https://github.com/ROCm/MIOpen#setting-up-locations) for more information. - -The System PerfDb is not modified upon installation of MIOpen. - -## Auto-tuning the kernels. - -MIOpen performs auto-tuning during the following MIOpen API calls: -- `miopenFindConvolutionForwardAlgorithm()` -- `miopenFindConvolutionBackwardDataAlgorithm()` -- `miopenFindConvolutionBackwardWeightsAlgorithm()` - -During the call, auto-tuning is performed only for one _problem configuration_ (implicitly defined by the tensor descriptors passed to API function). - -The following conditions must be met for the auto-tune to begin: -- The applicable kernel(s) has tuning parameters. -- The passed value of `exhaustiveSearch` parameter is `true`, and -- Both System and User PerfDb do not yet contain values for the relevant _problem configuration_. - -The latter two conditions may be overridden by _enforcing_ the search by means of the following environment variable: -- `MIOPEN_FIND_ENFORCE` - -This variable may also be used for _removing_ values from User PerfDb, see below. - -### MIOPEN_FIND_ENFORCE - -Both symbolic (case-insensitive) and numeric values are supported. - -**NONE (1)** - -Setting the value to "NONE", or "1" will have no change in the default behavior. - -**DB_UPDATE (2)** - -Auto-tune will not be skipped even if PerfDb already contains optimized values. If auto-tune is requested via API, then MIOpen will perform it and update PerfDb. - -This mode can be used for fine-tuning the MIOpen installation on the user's system. When MIOpen is in this mode, the applications that use it may take quite long to finish. - -**SEARCH (3)** - -MIOpen will perform auto-tune even if not requested via MIOpen API. In other words, the library will behave as if `exhaustiveSearch` parameter set to `true` even this is not really so. If optimized values already reside in PerfDb, then auto-tune will not be performed. - -This mode allows for tuning the apps that do not anticipate means for getting the best performance from MIOpen. When MIOpen is in this mode, the first run of the user's app may take substantially longer time than expected. - -**SEARCH_DB_UPDATE (4)** - -A combination of SEARCH and DB_UPDATE. MIOpen performs auto-tune (and updates User PerfDb) on each `miopenFindConvolution*()` call. It is not recommended to use this mode except for debugging purposes. - -**DB_CLEAN (5)** - -Use with care. MIOpen **removes** optimized values related to given _problem configuration_ from the User PerfDb. Auto-tune is blocked, even if it is explicitly requested. System PerfDb left intact. - -### Updating MIOpen and the User Db - -It is important to note that if the user installs a new version of MIOpen, it is recommended that the user move, or delete their old user performance database file. This will prevent older database entries from poluting the configurations shipped with the newer system database. The user perf db is named `miopen.udb` and is located at the user perf db path. diff --git a/docs/pooling.rst b/docs/pooling.rst deleted file mode 100644 index 412e69a5e8..0000000000 --- a/docs/pooling.rst +++ /dev/null @@ -1,87 +0,0 @@ - -Pooling Layer -============= - -The pooling layer API documentation - - -miopenPoolingMode_t -------------------- - -.. doxygenenum:: miopenPoolingMode_t - -miopenIndexType_t ------------------ - -.. doxygenenum:: miopenIndexType_t - -miopenCreatePoolingDescriptor ------------------------------ - -.. doxygenfunction:: miopenCreatePoolingDescriptor - -miopenSet2dPoolingDescriptor ----------------------------- - -.. doxygenfunction:: miopenSet2dPoolingDescriptor - -miopenSetNdPoolingDescriptor ----------------------------- - -.. doxygenfunction:: miopenSetNdPoolingDescriptor - -miopenSetPoolingIndexType -------------------------- - -.. doxygenfunction:: miopenSetPoolingIndexType - -miopenGetPoolingIndexType -------------------------- - -.. doxygenfunction:: miopenGetPoolingIndexType - -miopenGet2dPoolingDescriptor ----------------------------- - -.. doxygenfunction:: miopenGet2dPoolingDescriptor - -miopenGetNdPoolingDescriptor ----------------------------- - -.. doxygenfunction:: miopenGetNdPoolingDescriptor - -miopenGetPoolingForwardOutputDim --------------------------------- - -.. doxygenfunction:: miopenGetPoolingForwardOutputDim - -miopenGetPoolingNdForwardOutputDim ----------------------------------- - -.. doxygenfunction:: miopenGetPoolingNdForwardOutputDim - -miopenPoolingGetWorkSpaceSize ------------------------------ - -.. doxygenfunction:: miopenPoolingGetWorkSpaceSize - -miopenPoolingGetWorkSpaceSizeV2 -------------------------------- - -.. doxygenfunction:: miopenPoolingGetWorkSpaceSizeV2 - -miopenPoolingForward --------------------- - -.. doxygenfunction:: miopenPoolingForward - -miopenPoolingBackward ---------------------- - -.. doxygenfunction:: miopenPoolingBackward - -miopenDestroyPoolingDescriptor ------------------------------- - -.. doxygenfunction:: miopenDestroyPoolingDescriptor - diff --git a/docs/reduction.rst b/docs/reduction.rst deleted file mode 100644 index 5e89c7700b..0000000000 --- a/docs/reduction.rst +++ /dev/null @@ -1,72 +0,0 @@ - -Reduction Layer -=============== - -The reduction layer API documentation - - -miopenReduceTensorOp_t ----------------------- - -.. doxygenenum:: miopenReduceTensorOp_t - - -miopenNanPropagation_t ----------------------- - -.. doxygenenum:: miopenNanPropagation_t - - -miopenReduceTensorIndices_t ---------------------------- - -.. doxygenenum:: miopenReduceTensorIndices_t - - -miopenIndicesType_t -------------------- - -.. doxygenenum:: miopenIndicesType_t - - -miopenCreateReduceTensorDescriptor ----------------------------------- - -.. doxygenfunction:: miopenCreateReduceTensorDescriptor - - -miopenDestroyReduceTensorDescriptor ------------------------------------ - -.. doxygenfunction:: miopenDestroyReduceTensorDescriptor - - -miopenSetReduceTensorDescriptor -------------------------------- - -.. doxygenfunction:: miopenSetReduceTensorDescriptor - - -miopenGetReduceTensorDescriptor -------------------------------- - -.. doxygenfunction:: miopenGetReduceTensorDescriptor - - -miopenGetReductionIndicesSize ------------------------------ - -.. doxygenfunction:: miopenGetReductionIndicesSize - - -miopenGetReductionWorkspaceSize -------------------------------- - -.. doxygenfunction:: miopenGetReductionWorkspaceSize - - -miopenReduceTensor ------------------- - -.. doxygenfunction:: miopenReduceTensor - diff --git a/docs/reference/argmax.rst b/docs/reference/argmax.rst new file mode 100644 index 0000000000..001731b79c --- /dev/null +++ b/docs/reference/argmax.rst @@ -0,0 +1,16 @@ +.. meta:: + :description: MIOpen documentation + :keywords: MIOpen, ROCm, API, documentation + +******************************************************************** +Argmax Layer (experimental) +******************************************************************** + +Find the index of the maximum value of a tensor across dimensions. + +To enable this, define ``MIOPEN_BETA_API`` before including ``miopen.h``. + +miopenArgmaxForward +---------------------------------- + +.. doxygenfunction:: miopenArgmaxForward diff --git a/docs/reference/datatypes.rst b/docs/reference/datatypes.rst new file mode 100644 index 0000000000..dd2a91744b --- /dev/null +++ b/docs/reference/datatypes.rst @@ -0,0 +1,49 @@ +.. meta:: + :description: MIOpen API datatypes + :keywords: MIOpen, ROCm, API, documentation, datatypes + +******************************************************************** +Datatypes +******************************************************************** + +MIOpen contains several datatypes at different levels of support. The enumerated datatypes are: + +.. code:: cpp + + typedef enum { + miopenHalf = 0, + miopenFloat = 1, + miopenInt32 = 2, + miopenInt8 = 3, + /* Value 4 is reserved. */ + miopenBFloat16 = 5, + miopenDouble = 6, + miopenFloat8 = 7, + miopenBFloat8 = 8 + } miopenDataType_t; + +Of these types only ``miopenFloat`` and ``miopenHalf`` are fully supported across all layers in MIOpen. +Refer to the individual :doc:`Modules <../doxygen/html/modules>` in the API library for specific +datatype support and limitations. + +Type descriptions: + +* ``miopenHalf``: 16-bit floating point +* ``miopenFloat``: 32-bit floating point +* ``miopenInt32``: 32-bit integer, used primarily for ``int8`` convolution outputs +* ``miopenInt8``: 8-bit integer; supported by ``int8`` convolution forward path, tensor set, tensor copy, tensor cast, tensor transform, tensor transpose, and im2col +* ``miopenBFloat16``: brain float fp-16 (8-bit exponent, 7-bit fraction); supported by convolutions, tensor set, and tensor copy +* ``miopenDouble``: 64-bit floating point; supported by reduction, layerNorm, and batchNorm +* ``miopenFloat8``: 8-bit floating point (layout 1.4.3, exponent bias 7); supported by convolutions +* ``miopenBFloat8``: 8-bit floating point (layout 1.5.2, exponent bias 15); supported by convolutions + +In addition to these standard datatypes, pooling also contains its own indexing datatypes: + +.. code:: cpp + + typedef enum { + miopenIndexUint8 = 0, + miopenIndexUint16 = 1, + miopenIndexUint32 = 2, + miopenIndexUint64 = 3, + } miopenIndexType_t; diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 0000000000..02bcb88622 --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,34 @@ + +.. meta:: + :description: MIOpen API reference library + :keywords: MIOpen, ROCm, API, documentation + +************************************************************* +API reference library +************************************************************* + +The MIOpen API library is structured as follows: + +* :doc:`Datatypes <./datatypes>` + +* Modules: + + * :doc:`Handle <../doxygen/html/group__handle>` + * :doc:`Tensor <../doxygen/html/group__tensor>` + * :doc:`Activation <../doxygen/html/group__activation>` + * :doc:`Convolution <../doxygen/html/group__convolutions>` + * :doc:`Recurrent neural networks (RNN) <../doxygen/html/group___r_n_n>` + * :doc:`Batch normalization <../doxygen/html/group__batchnorm>` + * :doc:`Local response normalization (LRN) <../doxygen/html/group___l_r_n>` + * :doc:`Pooling <../doxygen/html/group__pooling>` + * :doc:`Softmax <../doxygen/html/group__softmax>` + * :doc:`Fusion <../doxygen/html/group___f_u_s_i_o_n>` + * :doc:`Loss function <../doxygen/html/group___loss_function>` + * :doc:`Dropout <../doxygen/html/group__dropout>` + * :doc:`Reduction <../doxygen/html/group___tensor_reduce>` + * :doc:`Find <../doxygen/html/group__find2>` + * :doc:`Layernorm <../doxygen/html/group__layernorm>` (experimental) + * :doc:`Sum <../doxygen/html/group__sum>` (experimental) + * :doc:`GroupNorm <../doxygen/html/group__groupnorm>` (experimental) + * :doc:`Cat <../doxygen/html/group__cat>` (experimental) + * :doc:`Argmax<./argmax>` (experimental) diff --git a/docs/rnn.rst b/docs/rnn.rst deleted file mode 100644 index 7f2adf9cfb..0000000000 --- a/docs/rnn.rst +++ /dev/null @@ -1,182 +0,0 @@ - -Recurrent Neural Networks -========================= - - -miopenRNNMode_t ---------------- - -.. doxygenenum:: miopenRNNMode_t - - -miopenRNNInputMode_t --------------------- - -.. doxygenenum:: miopenRNNInputMode_t - - -miopenRNNAlgo_t ---------------- - -.. doxygenenum:: miopenRNNAlgo_t - - -miopenRNNDirectionMode_t ------------------------- - -.. doxygenenum:: miopenRNNDirectionMode_t - - -miopenRNNBiasMode_t -------------------- - -.. doxygenenum:: miopenRNNBiasMode_t - - -miopenRNNGEMMalgoMode_t ------------------------ - -.. doxygenenum:: miopenRNNGEMMalgoMode_t - - -miopenCreateRNNDescriptor -------------------------- - -.. doxygenfunction:: miopenCreateRNNDescriptor - - -miopenGetRNNDescriptor ----------------------- - -.. doxygenfunction:: miopenGetRNNDescriptor - - -miopenGetRNNDescriptor_V2 -------------------------- - -.. doxygenfunction:: miopenGetRNNDescriptor_V2 - - -miopenDestroyRNNDescriptor --------------------------- - -.. doxygenfunction:: miopenDestroyRNNDescriptor - - -miopenSetRNNDescriptor ----------------------- - -.. doxygenfunction:: miopenSetRNNDescriptor - - -miopenSetRNNDescriptor_V2 -------------------------- - -.. doxygenfunction:: miopenSetRNNDescriptor_V2 - - -miopenGetRNNWorkspaceSize -------------------------- - -.. doxygenfunction:: miopenGetRNNWorkspaceSize - - -miopenGetRNNTrainingReserveSize -------------------------------- - -.. doxygenfunction:: miopenGetRNNTrainingReserveSize - - -miopenGetRNNParamsSize ----------------------- - -.. doxygenfunction:: miopenGetRNNParamsSize - - -miopenGetRNNParamsDescriptor ----------------------------- - -.. doxygenfunction:: miopenGetRNNParamsDescriptor - - -miopenGetRNNInputTensorSize ---------------------------- - -.. doxygenfunction:: miopenGetRNNInputTensorSize - - -miopenGetRNNHiddenTensorSize ----------------------------- - -.. doxygenfunction:: miopenGetRNNHiddenTensorSize - - -miopenGetRNNLayerParamSize --------------------------- - -.. doxygenfunction:: miopenGetRNNLayerParamSize - - -miopenGetRNNLayerBiasSize -------------------------- - -.. doxygenfunction:: miopenGetRNNLayerBiasSize - - -miopenGetRNNLayerParam ----------------------- - -.. doxygenfunction:: miopenGetRNNLayerParam - - -miopenGetRNNLayerBias ---------------------- - -.. doxygenfunction:: miopenGetRNNLayerBias - - -miopenSetRNNLayerParam ----------------------- - -.. doxygenfunction:: miopenSetRNNLayerParam - - -miopenSetRNNLayerBias ---------------------- - -.. doxygenfunction:: miopenSetRNNLayerBias - -miopenGetRNNLayerParamOffset ----------------------------- - -.. doxygenfunction:: miopenGetRNNLayerParamOffset - - -miopenGetRNNLayerBiasOffset ---------------------------- - -.. doxygenfunction:: miopenGetRNNLayerBiasOffset - -miopenRNNForwardTraining ------------------------- - -.. doxygenfunction:: miopenRNNForwardTraining - - -miopenRNNBackwardData ---------------------- - -.. doxygenfunction:: miopenRNNBackwardData - - -miopenRNNBackwardWeights ------------------------- - -.. doxygenfunction:: miopenRNNBackwardWeights - - -miopenRNNForwardInference -------------------------- - -.. doxygenfunction:: miopenRNNForwardInference - diff --git a/docs/softmax.rst b/docs/softmax.rst deleted file mode 100644 index bcbf715504..0000000000 --- a/docs/softmax.rst +++ /dev/null @@ -1,42 +0,0 @@ - - -Softmax Layer -============= - -Softmax type and layers - - -miopenSoftmaxAlgorithm_t ------------------------- - -.. doxygenenum:: miopenSoftmaxAlgorithm_t - - -miopenSoftmaxMode_t -------------------- - -.. doxygenenum:: miopenSoftmaxMode_t - - -miopenSoftmaxForward --------------------- - -.. doxygenfunction:: miopenSoftmaxForward - - -miopenSoftmaxBackward ---------------------- - -.. doxygenfunction:: miopenSoftmaxBackward - - -miopenSoftmaxForward_V2 ------------------------ - -.. doxygenfunction:: miopenSoftmaxForward_V2 - - -miopenSoftmaxBackward_V2 ------------------------- - -.. doxygenfunction:: miopenSoftmaxBackward_V2 diff --git a/docs/sphinx/.gitignore b/docs/sphinx/.gitignore new file mode 100644 index 0000000000..732e9711d2 --- /dev/null +++ b/docs/sphinx/.gitignore @@ -0,0 +1 @@ +_toc.yml diff --git a/docs/sphinx/_toc.yml.in b/docs/sphinx/_toc.yml.in index 0b1a03908e..24a87f6dcb 100644 --- a/docs/sphinx/_toc.yml.in +++ b/docs/sphinx/_toc.yml.in @@ -4,20 +4,52 @@ defaults: root: index subtrees: - entries: - - file: releasenotes - - file: install - - file: embed - - file: driver - - file: DebugAndLogging - - file: cache - - file: perfdatabase - - file: finddb - - file: find_and_immediate - - file: Getting_Started_FusionAPI - - file: MI200AlternateImplementation - - file: MIOpen_Porting_Guide - - file: apireference -- caption: About + - file: what-is-miopen.rst + title: What is MIOpen? + +- caption: Install + entries: + - file: install/install.rst + title: Install MIOpen + - file: install/docker-build.rst + title: Build MIOpen using Docker + - file: install/embed.rst + title: Build MIOpen for embedded systems + +- caption: Reference + entries: + - file: reference/index + title: API library + subtrees: + - entries: + - file: doxygen/html/modules + title: Modules + - file: reference/datatypes.rst + title: Datatypes + +- caption: Conceptual + entries: + - file: conceptual/finddb.rst + title: Find database + - file: conceptual/cache.rst + title: Cache + - file: conceptual/perfdb.rst + title: Performance database + - file: conceptual/MI200-alt-implementation.rst + title: MI200 alternate implementation + - file: conceptual/porting-guide.rst + title: Porting to MIOpen + +- caption: How to entries: - - file: license + - file: how-to/use-fusion-api.rst + title: Use Fusion API + - file: how-to/debug-log.rst + title: Log & debug + - file: how-to/find-and-immediate.rst + title: Use find APIs & immediate mode +- caption: About + entries: + - file: license.md + title: License diff --git a/docs/sphinx/requirements.in b/docs/sphinx/requirements.in index 6c61f38986..5671f15adc 100644 --- a/docs/sphinx/requirements.in +++ b/docs/sphinx/requirements.in @@ -1 +1 @@ -rocm-docs-core==0.34.2 +rocm-docs-core[api_reference]==0.38.1 diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt index 3c9dbd7487..7dcbd7f75c 100644 --- a/docs/sphinx/requirements.txt +++ b/docs/sphinx/requirements.txt @@ -4,31 +4,36 @@ # # pip-compile requirements.in # -accessible-pygments==0.0.3 +accessible-pygments==0.0.4 # via pydata-sphinx-theme alabaster==0.7.13 # via sphinx -babel==2.12.1 +babel==2.14.0 # via # pydata-sphinx-theme # sphinx -beautifulsoup4==4.11.2 +beautifulsoup4==4.12.3 # via pydata-sphinx-theme -breathe==4.34.0 +breathe==4.35.0 # via rocm-docs-core -certifi==2023.7.22 +certifi==2024.2.2 # via requests -cffi==1.15.1 +cffi==1.16.0 # via # cryptography # pynacl -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests -click==8.1.3 - # via sphinx-external-toc -cryptography==42.0.0 +click==8.1.7 + # via + # click-log + # doxysphinx + # sphinx-external-toc +click-log==0.4.0 + # via doxysphinx +cryptography==42.0.5 # via pyjwt -deprecated==1.2.13 +deprecated==1.2.14 # via pygithub docutils==0.19 # via @@ -36,62 +41,75 @@ docutils==0.19 # myst-parser # pydata-sphinx-theme # sphinx -fastjsonschema==2.16.3 +doxysphinx==3.3.7 # via rocm-docs-core -gitdb==4.0.10 +fastjsonschema==2.19.1 + # via rocm-docs-core +gitdb==4.0.11 # via gitpython -gitpython==3.1.41 +gitpython==3.1.42 # via rocm-docs-core -idna==3.4 +idna==3.7 # via requests imagesize==1.4.1 # via sphinx -importlib-metadata==6.8.0 +importlib-metadata==7.0.2 # via sphinx -importlib-resources==6.1.0 - # via rocm-docs-core +importlib-resources==6.1.3 + # via + # mpire + # rocm-docs-core jinja2==3.1.3 # via # myst-parser # sphinx +libsass==0.22.0 + # via doxysphinx +lxml==4.9.4 + # via doxysphinx markdown-it-py==2.2.0 # via # mdit-py-plugins # myst-parser -markupsafe==2.1.2 +markupsafe==2.1.5 # via jinja2 mdit-py-plugins==0.3.5 # via myst-parser mdurl==0.1.2 # via markdown-it-py +mpire==2.10.0 + # via doxysphinx myst-parser==1.0.0 # via rocm-docs-core -packaging==23.0 +packaging==24.0 # via # pydata-sphinx-theme # sphinx pycparser==2.21 # via cffi -pydata-sphinx-theme==0.13.3 +pydata-sphinx-theme==0.14.4 # via # rocm-docs-core # sphinx-book-theme -pygithub==1.58.1 +pygithub==2.2.0 # via rocm-docs-core -pygments==2.15.0 +pygments==2.17.2 # via # accessible-pygments + # mpire # pydata-sphinx-theme # sphinx +pyjson5==1.6.6 + # via doxysphinx pyjwt[crypto]==2.6.0 - # via - # pygithub - # pyjwt + # via pygithub pynacl==1.5.0 # via pygithub -pytz==2023.3.post1 +pyparsing==3.1.2 + # via doxysphinx +pytz==2024.1 # via babel -pyyaml==6.0 +pyyaml==6.0.1 # via # myst-parser # rocm-docs-core @@ -100,13 +118,13 @@ requests==2.31.0 # via # pygithub # sphinx -rocm-docs-core==0.34.2 +rocm-docs-core[api-reference]==0.38.1 # via -r requirements.in -smmap==5.0.0 +smmap==5.0.1 # via gitdb snowballstemmer==2.2.0 # via sphinx -soupsieve==2.4 +soupsieve==2.5 # via beautifulsoup4 sphinx==5.3.0 # via @@ -121,13 +139,13 @@ sphinx==5.3.0 # sphinx-notfound-page sphinx-book-theme==1.0.1 # via rocm-docs-core -sphinx-copybutton==0.5.1 +sphinx-copybutton==0.5.2 # via rocm-docs-core -sphinx-design==0.4.1 +sphinx-design==0.5.0 # via rocm-docs-core sphinx-external-toc==0.3.1 # via rocm-docs-core -sphinx-notfound-page==0.8.3 +sphinx-notfound-page==1.0.0 # via rocm-docs-core sphinxcontrib-applehelp==1.0.4 # via sphinx @@ -141,11 +159,17 @@ sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx -typing-extensions==4.5.0 - # via pydata-sphinx-theme -urllib3==1.26.18 - # via requests -wrapt==1.15.0 +tqdm==4.66.2 + # via mpire +typing-extensions==4.10.0 + # via + # pydata-sphinx-theme + # pygithub +urllib3==2.2.1 + # via + # pygithub + # requests +wrapt==1.16.0 # via deprecated zipp==3.17.0 # via diff --git a/docs/sum.rst b/docs/sum.rst deleted file mode 100644 index 6ec8d3ee4c..0000000000 --- a/docs/sum.rst +++ /dev/null @@ -1,23 +0,0 @@ - -Sum Layer(experimental) -======================== - -The sum types and functions. -To enable this, define MIOPEN_BETA_API before including miopen.h. - - -miopenSumNanPropagation_t ----------------------------------- - -.. doxygenenum:: miopenSumNanPropagation_t - -miopenGetSumWorkspaceSize ----------------------------------- - -.. doxygenfunction:: miopenGetSumWorkspaceSize - -miopenSumForward ----------------------------------- - -.. doxygenfunction:: miopenSumForward - diff --git a/docs/tensor.rst b/docs/tensor.rst deleted file mode 100644 index f056740231..0000000000 --- a/docs/tensor.rst +++ /dev/null @@ -1,66 +0,0 @@ - - -Tensors -======= - -Tensor types and functions. - -miopenDataType_t ----------------- - -.. doxygenenum:: miopenDataType_t - -miopenTensorOp_t ----------------- - -.. doxygenenum:: miopenTensorOp_t - -miopenCreateTensorDescriptor ----------------------------- - -.. doxygenfunction:: miopenCreateTensorDescriptor - -miopenSet4dTensorDescriptor ---------------------------- - -.. doxygenfunction:: miopenSet4dTensorDescriptor - -miopenGet4dTensorDescriptor ---------------------------- - -.. doxygenfunction:: miopenGet4dTensorDescriptor - -miopenSetTensorDescriptor -------------------------- - -.. doxygenfunction:: miopenSetTensorDescriptor - -miopenGetTensorDescriptorSize ------------------------------ - -.. doxygenfunction:: miopenGetTensorDescriptorSize - -miopenGetTensorDescriptor -------------------------- - -.. doxygenfunction:: miopenGetTensorDescriptor - -miopenDestroyTensorDescriptor ------------------------------ - -.. doxygenfunction:: miopenDestroyTensorDescriptor - -miopenOpTensor --------------- - -.. doxygenfunction:: miopenOpTensor - -miopenSetTensor ---------------- - -.. doxygenfunction:: miopenSetTensor - -miopenScaleTensor ------------------ - -.. doxygenfunction:: miopenScaleTensor \ No newline at end of file diff --git a/docs/what-is-miopen.rst b/docs/what-is-miopen.rst new file mode 100644 index 0000000000..e77678b583 --- /dev/null +++ b/docs/what-is-miopen.rst @@ -0,0 +1,16 @@ + +.. meta:: + :description: What is MIOpen? + :keywords: MIOpen, ROCm, API, documentation + +************************************************************* +What is MIOpen? +************************************************************* + +MIOpen is AMD's open-source, deep-learning primitives library for GPUs. It implements fusion to +optimize for memory bandwidth and GPU launch overheads, providing an auto-tuning infrastructure +to overcome the large design space of problem configurations. It also implements different algorithms +to optimize convolutions for different filter and input sizes. + +MIOpen is one of the first libraries to publicly support the bfloat16 datatype for convolutions, which +allows for efficient training at lower precision without loss of accuracy. diff --git a/driver/CBAInferFusion_driver.hpp b/driver/CBAInferFusion_driver.hpp index b24692c6cd..84be967d44 100644 --- a/driver/CBAInferFusion_driver.hpp +++ b/driver/CBAInferFusion_driver.hpp @@ -26,25 +26,30 @@ #ifndef GUARD_MIOPEN_CONV_BN_ACTIV_INFER_DRIVER_HPP #define GUARD_MIOPEN_CONV_BN_ACTIV_INFER_DRIVER_HPP -#include "../test/verify.hpp" #include "InputFlags.hpp" #include "driver.hpp" #include "miopen_ConvBatchNormActivHost.hpp" +#include "mloNeuronHost.hpp" +#include "random.hpp" #include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" + +#include "../test/verify.hpp" + +#include +#include +#include +#include + #include +#include #include #include #include #include -#include -#include -#include #include #include -#include -#include "random.hpp" -#include "mloNeuronHost.hpp" #define MIO_BN_DEBUG 0 #define MIO_BN_MAX_DEBUGLOOP 65536 @@ -58,14 +63,10 @@ #define RMSTOL_FP32 1e-4 #define RMSTOL_FP16 0.5e-3 -#ifdef MIOPEN_BACKEND_HIP -#ifndef CL_SUCCESS -#define CL_SUCCESS 0 -#endif -#endif - #define CBA_DEBUG_VALUES 0 +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DRIVER_PAD_BUFFERS_2M) + //"Fusion mode (cbna = 0, cna = 1, na = 2, cn = 3, cba = 4, ca = 5, cb = 6) (Default=cbna)", typedef enum { @@ -512,15 +513,13 @@ template int CBAInferFusionDriver::createSaveBuffers() { + status_t status = STATUS_SUCCESS; #if MIOPEN_BACKEND_OPENCL - cl_int status = CL_SUCCESS; cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - int status = 0; #endif - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; @@ -530,13 +529,10 @@ template int CBAInferFusionDriver::createRunningBuffers() { + status_t status = STATUS_SUCCESS; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_int status = CL_SUCCESS; - cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - int status = 0; - uint32_t ctx = 0; #endif if(useBatchNorm) @@ -566,7 +562,7 @@ int CBAInferFusionDriver::createRunningBuffers() // GPU data transfer status |= runningMean_dev->ToGPU(q, runningMean.data()); status |= runningVariance_dev->ToGPU(q, runningVariance.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) { printf("Error copying data to GPU\n"); exit(EXIT_FAILURE); // NOLINT (concurrency-mt-unsafe) @@ -584,13 +580,10 @@ template int CBAInferFusionDriver::AllocateBuffersAndCopy() { + status_t status = STATUS_SUCCESS; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_int status = CL_SUCCESS; - cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - int status = 0; - uint32_t ctx = 0; #endif size_t in_sz = GetTensorSize(inputTensor); @@ -648,8 +641,8 @@ int CBAInferFusionDriver::AllocateBuffersAndCopy() scale[i] = 1.; // prng::gen_canonical(); // 1.0; bias[i] = 10.; #else - scale[i] = prng::gen_canonical(); - bias[i] = prng::gen_canonical(); + scale[i] = prng::gen_canonical(); + bias[i] = prng::gen_canonical(); #endif } status |= scale_dev->ToGPU(q, scale.data()); @@ -674,9 +667,9 @@ int CBAInferFusionDriver::AllocateBuffersAndCopy() in_host[i] = static_cast(rval); in[i] = rval; #else - auto rval = prng::gen_canonical(); + auto rval = prng::gen_canonical(); in_host[i] = static_cast(rval); - in[i] = rval; + in[i] = rval; #endif } @@ -697,7 +690,7 @@ int CBAInferFusionDriver::AllocateBuffersAndCopy() status |= in_dev->ToGPU(q, in.data()); status |= createRunningBuffers(); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Fatal: Error copying data to GPU\nExiting...\n\n"); return miopenStatusSuccess; diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt index 7d4fdbb528..224e550fed 100644 --- a/driver/CMakeLists.txt +++ b/driver/CMakeLists.txt @@ -26,14 +26,41 @@ find_package(Threads REQUIRED) -add_executable(MIOpenDriver main.cpp InputFlags.cpp) +add_executable(MIOpenDriver + InputFlags.cpp + conv_common.cpp + dm_activ.cpp + dm_argmax.cpp + dm_bnorm.cpp + dm_cat.cpp + dm_conv.cpp + dm_convbfp16.cpp + dm_convbfp8.cpp + dm_convfp16.cpp + dm_convfp8.cpp + dm_convint8.cpp + dm_dropout.cpp + dm_fusion.cpp + dm_gemm.cpp + dm_groupnorm.cpp + dm_layernorm.cpp + dm_lrn.cpp + dm_pool.cpp + dm_reduce.cpp + dm_rnn.cpp + dm_softmax.cpp + dm_sum.cpp + dm_tensorop.cpp + main.cpp + registry_driver_maker.cpp + rocrand_wrapper.cpp) if(WIN32) # Refer to https://en.cppreference.com/w/cpp/language/types for details. target_compile_options(MIOpenDriver PRIVATE $:-U__LP64__>>) endif() add_dependencies(MIOpenDriver generate_kernels) target_include_directories(MIOpenDriver PRIVATE ../src/kernels) -target_link_libraries(MIOpenDriver MIOpen Threads::Threads) +target_link_libraries(MIOpenDriver MIOpen Threads::Threads roc::rocrand) if(NOT MIOPEN_EMBED_DB STREQUAL "") target_link_libraries(MIOpenDriver $ ) endif() diff --git a/driver/activ_driver.hpp b/driver/activ_driver.hpp index 529b44066b..d068e769cc 100644 --- a/driver/activ_driver.hpp +++ b/driver/activ_driver.hpp @@ -29,23 +29,20 @@ #include "InputFlags.hpp" #include "driver.hpp" #include "mloNeuronHost.hpp" +#include "random.hpp" #include "tensor_driver.hpp" +#include "timer.hpp" +#include "util_driver.hpp" + +#include +#include + #include -#include #include +#include #include -#include -#include #include #include -#include "random.hpp" -#include "timer.hpp" - -#ifdef MIOPEN_BACKEND_HIP -#ifndef CL_SUCCESS -#define CL_SUCCESS 0 -#endif -#endif template class ActivationDriver : public Driver @@ -198,12 +195,10 @@ int ActivationDriver::AllocateBuffersAndCopy() size_t in_sz = GetTensorSpace(inputTensor); size_t out_sz = GetTensorSpace(outputTensor); -#if MIOPEN_BACKEND_OPENCL - cl_context ctx; + DEFINE_CONTEXT(ctx); +#if MIOPEN_BACKEND_OPENCL clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif in_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); out_dev = std::unique_ptr(new GPUMem(ctx, out_sz, sizeof(Tgpu))); @@ -282,18 +277,14 @@ int ActivationDriver::AllocateBuffersAndCopy() dout[i] = prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif + status_t status; status = in_dev->ToGPU(q, in.data()); status |= out_dev->ToGPU(q, out.data()); status = din_dev->ToGPU(q, din.data()); status |= dout_dev->ToGPU(q, dout.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/argmax_driver.hpp b/driver/argmax_driver.hpp index d0ca256433..f3a8aed1ac 100644 --- a/driver/argmax_driver.hpp +++ b/driver/argmax_driver.hpp @@ -54,7 +54,7 @@ int32_t mloArgmaxForwardRunHost(miopenTensorDescriptor_t inputDesc, int32_t reduce_size = static_cast(input_dims[dim]); auto output_numel = - std::accumulate(output_dims.begin(), output_dims.end(), 1L, std::multiplies()); + std::accumulate(output_dims.begin(), output_dims.end(), 1LL, std::multiplies()); auto inner_size = std::accumulate( input_dims.begin() + dim + 1, input_dims.end(), 1ULL, std::multiplies()); diff --git a/driver/bn_driver.hpp b/driver/bn_driver.hpp index 912b4e17a9..4b94ac42d8 100644 --- a/driver/bn_driver.hpp +++ b/driver/bn_driver.hpp @@ -26,22 +26,27 @@ #ifndef GUARD_MIOPEN_BN_DRIVER_HPP #define GUARD_MIOPEN_BN_DRIVER_HPP -#include "../test/verify.hpp" #include "InputFlags.hpp" #include "driver.hpp" #include "miopen_BatchNormHost.hpp" +#include "random.hpp" +#include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" + +#include "../test/verify.hpp" + +#include +#include +#include + #include #include #include #include #include -#include -#include -#include #include #include -#include "random.hpp" #define MIO_BN_DEBUG 0 #define MIO_BN_MAX_DEBUGLOOP 65536 @@ -53,12 +58,6 @@ #define RMSTOL_FP32 1e-4 #define RMSTOL_FP16 0.5e-3 -#ifdef MIOPEN_BACKEND_HIP -#ifndef CL_SUCCESS -#define CL_SUCCESS 0 -#endif -#endif - #define MIO_DRIVER_BN_REFERENCE_COMPUTE_3D_AS_2D 1 // Resolves issue #1974 //#define BN_RUNFOR_PROFILER @@ -403,13 +402,10 @@ template int BatchNormDriver::createSaveBuffers() { + status_t status = STATUS_SUCCESS; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_int status = CL_SUCCESS; - cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - int status = 0; - uint32_t ctx = 0; #endif size_t sb_sz = GetTensorSize(biasScaleTensor); @@ -459,7 +455,7 @@ int BatchNormDriver::createSaveBuffers() saveInvVariance_dev = nullptr; } - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; @@ -468,14 +464,10 @@ int BatchNormDriver::createSaveBuffers() template int BatchNormDriver::createRunningBuffers() { - + status_t status = STATUS_SUCCESS; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_int status = CL_SUCCESS; - cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - int status = 0; - uint32_t ctx = 0; #endif size_t sb_sz = GetTensorSize(biasScaleTensor); @@ -524,7 +516,7 @@ int BatchNormDriver::createRunningBuffers() runningMean_dev = nullptr; runningVariance_dev = nullptr; } - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; @@ -533,14 +525,10 @@ int BatchNormDriver::createRunningBuffers() template int BatchNormDriver::AllocateBuffersAndCopy() { - + status_t status = STATUS_SUCCESS; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_int status = CL_SUCCESS; - cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - int status = 0; - uint32_t ctx = 0; #endif size_t in_sz = GetTensorSize(inputTensor); @@ -645,7 +633,7 @@ int BatchNormDriver::AllocateBuffersAndCopy() status |= createSaveBuffers(); } - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Fatal: Error copying data to GPU\nExiting...\n\n"); return miopenStatusSuccess; diff --git a/driver/conv_common.cpp b/driver/conv_common.cpp new file mode 100644 index 0000000000..0cbe426f1b --- /dev/null +++ b/driver/conv_common.cpp @@ -0,0 +1,71 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_common.hpp" + +namespace conv { + +// Shift FP16 distribution towards positive numbers, +// otherwise Winograd FP16 validation fails. +template <> +float16 RanGenWeights() +{ + return prng::gen_A_to_B(static_cast(-1.0 / 3.0), static_cast(0.5)); +} + +// int8 has it's own range +template <> +int8_t RanGenWeights() +{ + return prng::gen_A_to_B(static_cast(-1), static_cast(1)); +} + +template <> +float8 RanGenWeights() +{ + const auto tmp = + prng::gen_0_to_B(1.0) > 0.5 ? static_cast(0.0) : static_cast(1.0); + // 1 in 2 chance of number being positive + const float sign = + (prng::gen_0_to_B(1.0) > 0.5) ? static_cast(-1) : static_cast(1); + const auto tmp2 = static_cast(std::numeric_limits::epsilon()) * + static_cast(2) * sign * static_cast(tmp); + return static_cast(tmp2); +} + +template <> +bfloat8 RanGenWeights() +{ + const auto tmp = + prng::gen_0_to_B(1.0) > 0.5 ? static_cast(0.0) : static_cast(1.0); + // 1 in 2 chance of number being positive + const float sign = + (prng::gen_0_to_B(1.0) > 0.5) ? static_cast(-1) : static_cast(1); + const auto tmp2 = static_cast(std::numeric_limits::epsilon()) * + static_cast(2) * sign * static_cast(tmp); + return static_cast(tmp2); +} + +} // namespace conv diff --git a/driver/conv_common.hpp b/driver/conv_common.hpp new file mode 100644 index 0000000000..7f7bf4fac9 --- /dev/null +++ b/driver/conv_common.hpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#ifndef GUARD_DRIVER_CONV_COMMON_HPP +#define GUARD_DRIVER_CONV_COMMON_HPP + +/// This module is introduced in order to get rid of the followinf false linker warnings: +/// ld.lld: error: duplicate symbol: signed char detail::RanGenWeights() +/// >>> defined at conv1.cpp +/// >>> CMakeFiles/MIOpenDriver.dir/conv1.cpp.o:(signed char detail::RanGenWeights()) +/// >>> defined at conv3.cpp +/// >>> CMakeFiles/MIOpenDriver.dir/conv3.cpp.o:(.text+0x10) + +#include "random.hpp" + +#include + +#include +#include +using half = half_float::half; +using hip_bfloat16 = bfloat16; +#include + +using float16 = half_float::half; +using float8 = miopen_f8::hip_f8; +using bfloat8 = miopen_f8::hip_f8; + +namespace conv { + +template +T RanGenWeights() +{ + return prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); +} + +template <> +float16 RanGenWeights(); +template <> +int8_t RanGenWeights(); +template <> +float8 RanGenWeights(); +template <> +bfloat8 RanGenWeights(); + +} // namespace conv + +#endif // GUARD_DRIVER_CONV_COMMON_HPP diff --git a/driver/conv_driver.hpp b/driver/conv_driver.hpp index 9c2a3dee19..6e9e5fb0b6 100644 --- a/driver/conv_driver.hpp +++ b/driver/conv_driver.hpp @@ -26,52 +26,50 @@ #ifndef GUARD_MIOPEN_CONV_DRIVER_HPP #define GUARD_MIOPEN_CONV_DRIVER_HPP -/*#ifdef MIOPEN_NEURON_SOFTRELU /// \todo This needs to be explained or rewritten in clear manner. -#undef MIOPEN_NEURON_SOFTRELU -#endif - -#ifdef MIOPEN_NEURON_POWER /// \todo This needs to be explained or rewritten in clear manner. -#undef MIOPEN_NEURON_POWER -#endif -*/ #include "InputFlags.hpp" #include "conv_verify.hpp" +#include "conv_common.hpp" #include "driver.hpp" #include "mloConvHost.hpp" +#include "random.hpp" +#include "rocrand_wrapper.hpp" #include "tensor_driver.hpp" #include "timer.hpp" #include "util_driver.hpp" +#include "util_file.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include <../test/cpu_bias.hpp> +#include <../test/cpu_conv.hpp> +#include <../test/serialize.hpp> +#include <../test/tensor_holder.hpp> +#include <../test/verify.hpp> + +#include +#include +#include + #include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "random.hpp" #include #include -#include #include -#include -#include -#include <../test/verify.hpp> -#include <../test/serialize.hpp> -#include <../test/tensor_holder.hpp> -#include <../test/cpu_conv.hpp> -#include <../test/cpu_bias.hpp> - -#include +#include // Declare hidden function for MIGraphX to smoke test it. extern "C" MIOPEN_EXPORT miopenStatus_t @@ -87,14 +85,6 @@ MIOPEN_DECLARE_ENV_VAR_UINT64(MIOPEN_DRIVER_SUBNORM_PERCENTAGE) // for reference in the future. #define miopenInt8x4 (static_cast(4)) -#if MIOPEN_BACKEND_OPENCL -#define STATUS_SUCCESS CL_SUCCESS -typedef cl_int status_t; -#else // MIOPEN_BACKEND_HIP -#define STATUS_SUCCESS 0 -typedef int status_t; -#endif - struct AutoMiopenWarmupMode { AutoMiopenWarmupMode() @@ -147,22 +137,6 @@ struct AutoPrepareForGpuReference bool quiet_prev; }; -template -void dumpBufferToFile(const char* fileName, T* data, size_t dataNumItems) -{ - std::ofstream outFile(fileName, std::ios::binary); - if(outFile) - { - outFile.write(reinterpret_cast(data), dataNumItems * sizeof(T)); - outFile.close(); - printf("Wrote output to file %s\n", fileName); - } - else - { - printf("Could not open file %s for writing\n", fileName); - } -} - static inline miopenDataType_t DataTypeFromShortString(const std::string& type) { static const std::unordered_map conv_map = { @@ -183,34 +157,197 @@ static inline miopenDataType_t DataTypeFromShortString(const std::string& type) } } -template -bool readBufferFromFile(T* data, size_t dataNumItems, const char* fileName) +template +class GpumemTensor { - std::ifstream infile(fileName, std::ios::binary); - if(infile) + std::unique_ptr dev; + tensor host; + bool is_gpualloc = false; + +public: + void SetGpuallocMode(bool v) { is_gpualloc = v; } + tensor& GetTensor() { return host; } + + void AllocOnHost(miopenTensorDescriptor_t t) { - infile.read(reinterpret_cast(data), dataNumItems * sizeof(T)); - infile.close(); - printf("Read data from input file %s\n", fileName); - return true; + host = tensor(miopen::deref(t)); + if(is_gpualloc) // We do not need host data. + { + host.data.clear(); + host.data.shrink_to_fit(); // To free host memory. + } } - else + + std::vector& GetVector() { - printf("Could not open file %s for reading\n", fileName); - return false; + if(is_gpualloc) + MIOPEN_THROW("[MIOpenDriver] GpumemTensor::GetVector should not be called in " + "'--gpualloc 1' mode"); + return host.data; + } + + Tgpu* GetVectorData() { return is_gpualloc ? nullptr : host.data.data(); } + std::size_t GetVectorSize() const { return is_gpualloc ? 0 : host.data.size(); } + + void + InitHostData(const size_t sz, // + const bool do_write, // If set to false, then only generate random data. This is + // necessary to reproduce values in input buffers even if some + // directions are skipped. For example, inputs for Backward + // will be the same for both "-F 0" and "-F 2". + std::function generator) + { + if(is_gpualloc) + { + /// In gpualloc mode, we do not care about reproducibility of results, because + /// validation is not used. Therefore, we do not have to always generate random value + /// (\ref move_rand) + return; + } + + for(size_t i = 0; i < sz; ++i) + { + /// \anchor move_rand + /// Generate random value, even if buffer is unused. This provides the same + /// initialization of input buffers regardless of which kinds of + /// convolutions are currently selectedfor testing (see the "-F" option). + /// Verification cache would be broken otherwise. + auto val = generator(); + if(do_write) + GetVector()[i] = val; + } + } + + status_t AllocOnDevice(stream, context_t ctx, const size_t sz) + { + dev = std::make_unique(ctx, sz, sizeof(Tgpu)); + return STATUS_SUCCESS; + } + + status_t AllocOnDeviceAndInit(stream q, context_t ctx, const size_t sz) + { + AllocOnDevice(q, ctx, sz); + if(is_gpualloc) + { + /// \anchor gpualloc_random_init + /// In gpualloc mode, we do not want to leave input buffers uninitialized, because + /// there could be NaNs and Infs, which may affect the performance (which we are + /// interested to evaluate in this mode). Initialization with all 0's is not the + /// best choice as well, because GPU HW may optimize out computations with 0's and + /// that could affect performance of kernels too. That is why we are using + /// rocrand to initialize input buffers. + /// + /// However we do not care about precision in gpualloc mode, because validation + /// is not used. Therefore, range (0,1] is fine. + return gpumemrand::gen_0_1(static_cast(GetDevicePtr()), sz); + } + return dev->ToGPU(q, GetVectorData()); + } + + template + status_t AllocOnDevice(stream, context_t ctx, const size_t sz, std::vector&) + { + static_assert(std::is_same::value // + || std::is_same::value, // + "Before enabling more types, check thoroughly."); + dev = std::make_unique(ctx, sz, sizeof(T)); + return STATUS_SUCCESS; + } + + template + status_t AllocOnDeviceAndInit(stream q, context_t ctx, const size_t sz, std::vector& init) + { + AllocOnDevice(q, ctx, sz, init); + if(is_gpualloc) + { + /// \ref gpualloc_random_init + return gpumemrand::gen_0_1(static_cast(GetDevicePtr()), sz); + } + return dev->ToGPU(q, init.data()); + } + + status_t CopyFromDeviceToHost(stream q) + { + return is_gpualloc ? STATUS_SUCCESS : dev->FromGPU(q, GetVectorData()); + } + + template + status_t CopyFromDeviceToHost(stream q, tensor& t) + { + return is_gpualloc ? STATUS_SUCCESS : dev->FromGPU(q, t.data.data()); + } + + template + status_t CopyFromDeviceToHost(stream q, std::vector& v) + { + return is_gpualloc ? STATUS_SUCCESS : dev->FromGPU(q, v.data()); + } + + auto GetDevicePtr() -> auto { return dev->GetMem(); } +}; + +template +class GpumemVector +{ + std::unique_ptr dev; + std::vector host; + bool is_gpualloc = false; + +public: + void SetGpuallocMode(bool v) { is_gpualloc = v; } + void AllocOnHost(std::size_t sz) + { + if(is_gpualloc) // We do not need host data. + return; + host = std::vector(sz, static_cast(0)); + } + std::vector& GetVector() + { + if(is_gpualloc) + MIOPEN_THROW("[MIOpenDriver] GpumemVector::GetVector should not be called in " + "'--gpualloc 1' mode"); + return host; + } + + Tgpu* GetVectorData() { return is_gpualloc ? nullptr : host.data(); } + std::size_t GetVectorSize() const { return is_gpualloc ? 0 : host.size(); } + + status_t AllocOnDevice(stream, context_t ctx, const size_t sz) + { + dev = std::make_unique(ctx, sz, sizeof(Tgpu)); + return STATUS_SUCCESS; + } + + status_t AllocOnDeviceAndInit(stream q, context_t ctx, const size_t sz) + { + AllocOnDevice(q, ctx, sz); + if(is_gpualloc) + { + /// \ref gpumem_random_init + return gpumemrand::gen_0_1(static_cast(GetDevicePtr()), sz); + } + return dev->ToGPU(q, GetVectorData()); + } + + status_t CopyFromDeviceToHost(stream q) + { + return is_gpualloc ? STATUS_SUCCESS : dev->FromGPU(q, GetVectorData()); + } + + template + status_t CopyFromDeviceToHost(stream q, tensor& t) + { + return is_gpualloc ? STATUS_SUCCESS : dev->FromGPU(q, t.data.data()); } -} + + auto GetDevicePtr() -> auto { return dev->GetMem(); } +}; // Tgpu and Tref are the data-type in GPU memory and CPU memory respectively. // They are not necessarily the same as the computation type on GPU or CPU template class ConvDriver : public Driver { -#if MIOPEN_BACKEND_OPENCL - typedef cl_context context_t; -#elif MIOPEN_BACKEND_HIP - typedef uint32_t context_t; -#endif public: ConvDriver() : Driver() { @@ -317,6 +454,18 @@ class ConvDriver : public Driver boost::optional immediate_solution; + GpumemTensor in; + GpumemVector din; + GpumemTensor wei; + GpumemVector dwei; + GpumemTensor out; + GpumemTensor dout; + GpumemTensor b; + GpumemVector db; + GpumemTensor warmup_in; + GpumemTensor warmup_wei; + GpumemTensor warmup_out; + miopenTensorDescriptor_t inputTensor; miopenTensorDescriptor_t weightTensor; miopenTensorDescriptor_t outputTensor; @@ -327,19 +476,8 @@ class ConvDriver : public Driver miopenTensorDescriptor_t warmupWeightTensor; miopenTensorDescriptor_t warmupOutputTensor; - std::unique_ptr in_dev; std::unique_ptr in_vect4_dev; - std::unique_ptr din_dev; - std::unique_ptr wei_dev; std::unique_ptr wei_vect4_dev; - std::unique_ptr dwei_dev; - std::unique_ptr out_dev; - std::unique_ptr dout_dev; - std::unique_ptr b_dev; - std::unique_ptr db_dev; - std::unique_ptr warmup_in_dev; - std::unique_ptr warmup_wei_dev; - std::unique_ptr warmup_out_dev; std::unique_ptr workspace_dev; std::size_t ws_sizeof_find_fwd; @@ -347,23 +485,12 @@ class ConvDriver : public Driver std::size_t ws_sizeof_find_wrw; std::size_t warmup_ws_sizeof_find; - tensor in; - tensor wei; - tensor out; - tensor dout; - tensor b; tensor outhost; tensor dwei_host; tensor din_host; tensor db_host; - tensor warmup_in; - tensor warmup_wei; - tensor warmup_out; - std::vector din; - std::vector dwei; std::vector out_int8; - std::vector db; std::vector b_int8; miopenConvolutionDescriptor_t convDesc; @@ -378,6 +505,7 @@ class ConvDriver : public Driver bool time_enabled = false; bool wall_enabled = false; bool warmup_enabled = false; + bool is_gpualloc = false; int num_iterations = 1; // Used to avoid wasting time for verification after failure of Run*GPU(). @@ -545,17 +673,19 @@ int ConvDriver::ParseCmdLineArgs(int argc, char* argv[]) inflags.GetValueStr("out_layout") + "c" + std::to_string(vector_length)); } - if((inflags.GetValueStr("mode")) == "conv") + if(inflags.GetValueStr("mode") == "conv") { mode = miopenConvolution; } - else if((inflags.GetValueStr("mode")) == "trans") + else if(inflags.GetValueStr("mode") == "trans") { mode = miopenTranspose; } else { - MIOPEN_THROW("Incorrect Convolution Mode\n"); + std::cout << "Incorrect Convolution Mode: '" << inflags.GetValueStr("mode") << '\'' + << std::endl; + return 1; } num_iterations = inflags.GetValueInt("iter"); @@ -633,6 +763,29 @@ int ConvDriver::ParseCmdLineArgs(int argc, char* argv[]) return 1; } } + + is_gpualloc = (inflags.GetValueInt("gpualloc") == 1); + + if(is_gpualloc && inflags.GetValueInt("verify") == 1) + { + std::cerr << "Error: '--gpualloc 1' should not be used with enabled verification. Add " + "'--verify 0' to options." + << std::endl; + exit(EXIT_FAILURE); + } + + in.SetGpuallocMode(is_gpualloc); + din.SetGpuallocMode(is_gpualloc); + wei.SetGpuallocMode(is_gpualloc); + dwei.SetGpuallocMode(is_gpualloc); + out.SetGpuallocMode(is_gpualloc); + dout.SetGpuallocMode(is_gpualloc); + b.SetGpuallocMode(is_gpualloc); + db.SetGpuallocMode(is_gpualloc); + warmup_in.SetGpuallocMode(is_gpualloc); + warmup_wei.SetGpuallocMode(is_gpualloc); + warmup_out.SetGpuallocMode(is_gpualloc); + return 0; } @@ -789,7 +942,7 @@ int ConvDriver::GetandSetData() static_cast(miopenConvolutionFindModeNormal)); // Repeat via hidden API. miopenSetConvolutionGroupCount(warmupConvDesc, group_count); - int warmup_out_len_size = miopen::deref(warmupInputTensor).GetSize(); + int warmup_out_len_size = miopen::deref(warmupInputTensor).GetNumDims(); std::vector warmup_out_len(warmup_out_len_size); miopenGetConvolutionNdForwardOutputDim(warmupConvDesc, warmupInputTensor, @@ -877,7 +1030,7 @@ int ConvDriver::AddCmdLineArgs() "Wall-clock Time Each Layer" "\n0 Off (Default)" "\n1 On, requires '--time 1')" - "\n2 On, warm-up the library (prefetch db caches), requires '--time 1')", + "\n2 On, warm-up the library (prefetch db caches), requires '--time 1'", "int"); inflags.AddInputFlag("search", 's', "0", "Search Kernel Config (Default=0)", "int"); inflags.AddInputFlag("printconv", 'P', "1", "Print Convolution Dimensions (Default=1)", "int"); @@ -919,6 +1072,15 @@ int ConvDriver::AddCmdLineArgs() "\n Immediate mode, build and run specified solution" "\n Use Find() API", "string"); + inflags.AddInputFlag("gpualloc", + 'G', + "0", + "Controls allocation and initialization buffers on GPU and CPU." + "\n0 Init input buffers on CPU and copy them to GPU. After convolution" + "\n is executed, copy output buffer to CPU (Default)." + "\n1 No copying. Use hipMalloc to allocate and rocrand to init buffers" + "\n directly on GPU. Verification (-V 1) won't succeed in this mode.", + "int"); inflags.AddInputFlag( "in_cast_type", 'U', "-1", "Cast type for input tensor, default to not set", "string"); inflags.AddInputFlag( @@ -1135,7 +1297,7 @@ int ConvDriver::SetConvDescriptorFromCmdLineArgs() template std::vector ConvDriver::GetOutputTensorLengths() { - int ndim = miopen::deref(inputTensor).GetSize(); + int ndim = miopen::deref(inputTensor).GetNumDims(); std::vector out_lens(ndim); @@ -1145,28 +1307,7 @@ std::vector ConvDriver::GetOutputTensorLengths() return out_lens; } -namespace detail { - -template -T RanGenWeights() -{ - return prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); -} - -// Shift FP16 distribution towards positive numbers, -// otherwise Winograd FP16 validation fails. -template <> -float16 RanGenWeights() -{ - return prng::gen_A_to_B(static_cast(-1.0 / 3.0), static_cast(0.5)); -} - -// int8 has it's own range -template <> -int8_t RanGenWeights() -{ - return prng::gen_A_to_B(static_cast(-1), static_cast(1)); -} +namespace { template void RanGenSubnormBuffer(T* buf, size_t size, int percentage) @@ -1183,33 +1324,7 @@ void RanGenSubnormBuffer(T* buf, size_t size, int percentage) }); } -template <> -float8 RanGenWeights() -{ - const auto tmp = - prng::gen_0_to_B(1.0) > 0.5 ? static_cast(0.0) : static_cast(1.0); - // 1 in 2 chance of number being positive - const float sign = - (prng::gen_0_to_B(1.0) > 0.5) ? static_cast(-1) : static_cast(1); - const auto tmp2 = static_cast(std::numeric_limits::epsilon()) * - static_cast(2) * sign * static_cast(tmp); - return static_cast(tmp2); -} - -template <> -bfloat8 RanGenWeights() -{ - const auto tmp = - prng::gen_0_to_B(1.0) > 0.5 ? static_cast(0.0) : static_cast(1.0); - // 1 in 2 chance of number being positive - const float sign = - (prng::gen_0_to_B(1.0) > 0.5) ? static_cast(-1) : static_cast(1); - const auto tmp2 = static_cast(std::numeric_limits::epsilon()) * - static_cast(2) * sign * static_cast(tmp); - return static_cast(tmp2); -} - -} // namespace detail +} // namespace template int ConvDriver::AllocateBuffersAndCopy() @@ -1255,12 +1370,9 @@ int ConvDriver::AllocateBuffersAndCopy() PadBufferSize(out_sz, sizeof(Tgpu)); } + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif ws_sizeof_find_fwd = 0; ws_sizeof_find_wrw = 0; @@ -1304,24 +1416,14 @@ int ConvDriver::AllocateBuffersAndCopy() break; } - warmup_in = tensor(miopen::deref(warmupInputTensor).GetLengths(), - miopen::deref(warmupInputTensor).GetStrides()); - warmup_wei = tensor(miopen::deref(warmupWeightTensor).GetLengths(), - miopen::deref(warmupWeightTensor).GetStrides()); - warmup_out = tensor(miopen::deref(warmupOutputTensor).GetLengths(), - miopen::deref(warmupOutputTensor).GetStrides()); - - warmup_in_dev = - std::unique_ptr(new GPUMem(ctx, warmup_in_sz, sizeof(warmup_Tgpu))); - warmup_wei_dev = - std::unique_ptr(new GPUMem(ctx, warmup_wei_sz, sizeof(warmup_Tgpu))); - warmup_out_dev = - std::unique_ptr(new GPUMem(ctx, warmup_out_sz, sizeof(warmup_Tgpu))); + warmup_in.AllocOnHost(warmupInputTensor); + warmup_wei.AllocOnHost(warmupWeightTensor); + warmup_out.AllocOnHost(warmupOutputTensor); status_t status = STATUS_SUCCESS; - status |= warmup_in_dev->ToGPU(q, warmup_in.data.data()); - status |= warmup_wei_dev->ToGPU(q, warmup_wei.data.data()); - status |= warmup_out_dev->ToGPU(q, warmup_out.data.data()); + status |= warmup_in.AllocOnDeviceAndInit(q, ctx, warmup_in_sz); + status |= warmup_wei.AllocOnDeviceAndInit(q, ctx, warmup_wei_sz); + status |= warmup_out.AllocOnDeviceAndInit(q, ctx, warmup_out_sz); if(status != STATUS_SUCCESS) { @@ -1393,18 +1495,18 @@ int ConvDriver::AllocateBuffersAndCopy() } if(is_fwd || is_wrw) - in = tensor(miopen::deref(inputTensor)); + in.AllocOnHost(inputTensor); if(is_fwd || is_bwd) - wei = tensor(miopen::deref(weightTensor)); + wei.AllocOnHost(weightTensor); if(is_fwd) - out = tensor(miopen::deref(outputTensor)); + out.AllocOnHost(outputTensor); if(is_bwd || is_wrw) - dout = tensor(miopen::deref(outputTensor)); + dout.AllocOnHost(outputTensor); if(is_bwd) - din = std::vector(in_sz, static_cast(0)); + din.AllocOnHost(in_sz); if(is_wrw) - dwei = std::vector(wei_sz, static_cast(0)); + dwei.AllocOnHost(wei_sz); if(is_int8) out_int8 = std::vector(out_sz, 0); if(is_transform) @@ -1433,12 +1535,12 @@ int ConvDriver::AllocateBuffersAndCopy() bool dataRead = false; if(is_fwd || is_wrw) if(!inFileName.empty()) - dataRead = readBufferFromFile(in.data.data(), in_sz, inFileName.c_str()); + dataRead = readBufferFromFile(in.GetVectorData(), in_sz, inFileName.c_str()); bool weiRead = false; if(is_fwd || is_bwd) if(!weiFileName.empty()) - weiRead = readBufferFromFile(wei.data.data(), wei_sz, weiFileName.c_str()); + weiRead = readBufferFromFile(wei.GetVectorData(), wei_sz, weiFileName.c_str()); const Tgpu Data_scale = is_int8 ? static_cast(127) : (is_fp8 ? static_cast(1.0) : static_cast(0.01)); @@ -1449,19 +1551,18 @@ int ConvDriver::AllocateBuffersAndCopy() if(inflags.GetValueInt("bias") != 0) { size_t b_sz = GetTensorSize(biasTensor); - b_dev = std::unique_ptr(new GPUMem(ctx, b_sz, sizeof(float))); b_int8 = std::vector(b_sz, 0.f); - for(int i = 0; i < b_sz; i++) - { - b_int8[i] = static_cast(i % 8) + prng::gen_canonical(); - } - if(!biasFileName.empty()) + if(!is_gpualloc) { - readBufferFromFile(b_int8.data(), b_sz, biasFileName.c_str()); + bool read = false; + if(!biasFileName.empty()) + read = readBufferFromFile(b_int8.data(), b_sz, biasFileName.c_str()); + if(!read) + for(size_t i = 0; i < b_sz; ++i) + b_int8[i] = static_cast(i % 8) + prng::gen_canonical(); } - - b_dev->ToGPU(q, b_int8.data()); + std::ignore = b.AllocOnDeviceAndInit(q, ctx, b_sz, b_int8); } } else @@ -1470,130 +1571,125 @@ int ConvDriver::AllocateBuffersAndCopy() bool doutRead = false; if(is_bwd || is_wrw) if(!doutFileName.empty()) - doutRead = readBufferFromFile(dout.data.data(), out_sz, doutFileName.c_str()); + doutRead = + readBufferFromFile(dout.GetVectorData(), out_sz, doutFileName.c_str()); if(!doutRead) { - for(int i = 0; i < out_sz; i++) + auto gen = [&]() -> auto { - /// \anchor move_rand - /// Generate random value, even if buffer is unused. This provides the same - /// initialization of input buffers regardless of which kinds of - /// convolutions are currently selectedfor testing (see the "-F" option). - /// Verification cache would be broken otherwise. - auto val = - is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) : prng::gen_0_to_B(Data_scale); - if(is_bwd || is_wrw) - dout.data[i] = val; - } + return is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) : prng::gen_0_to_B(Data_scale); + }; + dout.InitHostData(out_sz, is_bwd || is_wrw, gen); } if(is_wrw) - detail::RanGenSubnormBuffer(dout.data.data(), out_sz, subnorm_percentage); + if(!is_gpualloc) + RanGenSubnormBuffer(dout.GetVectorData(), out_sz, subnorm_percentage); if(inflags.GetValueInt("bias") != 0) { size_t b_sz = GetTensorSize(biasTensor); - b_dev = std::unique_ptr(new GPUMem(ctx, b_sz, sizeof(Tgpu))); - db_dev = std::unique_ptr(new GPUMem(ctx, b_sz, sizeof(Tgpu))); - b = tensor(miopen::deref(biasTensor)); - db = std::vector(b_sz, static_cast(0)); - db_host = tensor(miopen::deref(biasTensor)); - for(int i = 0; i < b_sz; i++) - { - b.data[i] = - static_cast(i % 8) + - (is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) : prng::gen_canonical()); - db[i] = static_cast(i % 8) + (is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) - : prng::gen_canonical()); - } + b.AllocOnHost(biasTensor); + db.AllocOnHost(b_sz); + db_host = tensor(miopen::deref(biasTensor)); + + // Init tensor on host + bool b_read = false; if(!biasFileName.empty()) + b_read = readBufferFromFile(b.GetVectorData(), b_sz, biasFileName.c_str()); + + if(!is_gpualloc) { - readBufferFromFile(b.data.data(), b_sz, biasFileName.c_str()); + for(size_t i = 0; i < b_sz; ++i) + { + if(!b_read) + { + /// (i % 8) can't be converted to F8 type as there is no suitable + /// conversion, but we have conversions from int and from uint8_t. + /// int is not good as it would produce negative results + /// after truncation of size_t, while we want positive values. + /// uint8_t is fine because (i % 8) fits into 3 bits. + b.GetVector()[i] = static_cast(static_cast(i) % 8) // + + (is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) // + : prng::gen_canonical()); + } + db.GetVector()[i] = static_cast(static_cast(i) % 8) // + + (is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) // + : prng::gen_canonical()); + } } - b_dev->ToGPU(q, b.data.data()); - db_dev->ToGPU(q, db.data()); + b.AllocOnDeviceAndInit(q, ctx, b_sz); + db.AllocOnDeviceAndInit(q, ctx, b_sz); } } if(!dataRead) { - for(int i = 0; i < in_sz; i++) - { - /// \ref move_rand - auto val = is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) : prng::gen_0_to_B(Data_scale); - if(is_fwd || is_wrw) - in.data[i] = val; - } + auto gen = [&]() -> Tgpu { + return is_fp8 ? prng::gen_A_to_B(Data_min, Data_max) : prng::gen_0_to_B(Data_scale); + }; + in.InitHostData(in_sz, is_fwd || is_wrw, gen); } if(!weiRead) { - for(int i = 0; i < wei_sz; i++) - { - /// \ref move_rand - auto w = Data_scale * detail::RanGenWeights(); - if(is_fwd || is_bwd) - wei.data[i] = w; - } + auto gen = [&]() -> auto { return Data_scale * conv::RanGenWeights(); }; + wei.InitHostData(wei_sz, is_fwd || is_bwd, gen); } if(is_fwd || is_bwd) - detail::RanGenSubnormBuffer(wei.data.data(), wei_sz, subnorm_percentage); + if(!is_gpualloc) + RanGenSubnormBuffer(wei.GetVectorData(), wei_sz, subnorm_percentage); if(inflags.GetValueInt("dump_output")) { if(is_fwd || is_wrw) - dumpBufferToFile("dump_in.bin", in.data.data(), in_sz); + dumpBufferToFile("dump_in.bin", in.GetVectorData(), in_sz); if(is_fwd || is_bwd) - dumpBufferToFile("dump_wei.bin", wei.data.data(), wei_sz); + dumpBufferToFile("dump_wei.bin", wei.GetVectorData(), wei_sz); if(inflags.GetValueInt("bias") != 0) - dumpBufferToFile("dump_bias.bin", b.data.data(), b.data.size()); - + dumpBufferToFile("dump_bias.bin", b.GetVectorData(), b.GetVectorSize()); if(is_bwd || is_wrw) - dumpBufferToFile("dump_dout.bin", dout.data.data(), out_sz); + dumpBufferToFile("dump_dout.bin", dout.GetVectorData(), out_sz); } status_t status = STATUS_SUCCESS; if(is_fwd || is_wrw) { - in_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); - status |= in_dev->ToGPU(q, in.data.data()); + status |= in.AllocOnDeviceAndInit(q, ctx, in_sz); } if(is_bwd) { - din_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); - status |= din_dev->ToGPU(q, din.data()); + status |= din.AllocOnDevice(q, ctx, in_sz); } if(is_fwd || is_bwd) { - wei_dev = std::unique_ptr(new GPUMem(ctx, wei_sz, sizeof(Tgpu))); - status |= wei_dev->ToGPU(q, wei.data.data()); + status |= wei.AllocOnDeviceAndInit(q, ctx, wei_sz); } if(is_wrw) { - dwei_dev = std::unique_ptr(new GPUMem(ctx, wei_sz, sizeof(Tgpu))); - status |= dwei_dev->ToGPU(q, dwei.data()); + status |= dwei.AllocOnDevice(q, ctx, wei_sz); } if(is_bwd || is_wrw) { - dout_dev = std::unique_ptr(new GPUMem(ctx, out_sz, sizeof(Tgpu))); - status |= dout_dev->ToGPU(q, dout.data.data()); + status |= dout.AllocOnDeviceAndInit(q, ctx, out_sz); } if(is_fwd) { - // TODO: For the temporary conversion to half, this is required, however, that would also - // need change elsewhere which has not yet been implemented out_dev = - // std::unique_ptr(new GPUMem( - // ctx, out_sz, is_int8 ? sizeof(float) : (is_fp8 ? sizeof(half) : sizeof(Tgpu)))); + /// \todo: For the temporary conversion to half, this is required, however, that would also + /// need change elsewhere which has not yet been implemented: + /// + /// out_dev = ... (is_fp8 ? sizeof(half) : sizeof(Tgpu)) + /// + /// \note The above todo is necessary only when tensor casting is used. --atamazov Feb 2024 std::ignore = is_fp8; - out_dev = std::unique_ptr( - new GPUMem(ctx, out_sz, is_int8 ? sizeof(float) : sizeof(Tgpu))); - status |= - (is_int8 ? out_dev->ToGPU(q, out_int8.data()) : out_dev->ToGPU(q, out.data.data())); + + status |= is_int8 ? out.AllocOnDevice(q, ctx, out_sz, out_int8) // + : out.AllocOnDevice(q, ctx, out_sz); } if(status != STATUS_SUCCESS) @@ -1634,12 +1730,12 @@ int ConvDriver::FindForward(int& ret_algo_count, const auto rc = miopenFindConvolutionForwardAlgorithm( GetHandle(), (is_transform ? inputTensor_vect4 : inputTensor), - (is_transform ? in_vect4_dev->GetMem() : in_dev->GetMem()), + (is_transform ? in_vect4_dev->GetMem() : in.GetDevicePtr()), (is_transform ? weightTensor_vect4 : weightTensor), - (is_transform ? wei_vect4_dev->GetMem() : wei_dev->GetMem()), + (is_transform ? wei_vect4_dev->GetMem() : wei.GetDevicePtr()), convDesc, outputTensor, - out_dev->GetMem(), + out.GetDevicePtr(), request_algo_count, &ret_algo_count, perf_results.data(), @@ -1659,7 +1755,7 @@ void ConvDriver::PrintForwardTime(const float kernel_total_time, : kernel_first_time; printf("GPU Kernel Time Forward Conv. Elapsed: %f ms (average)\n", kernel_average_time); - const auto num_dim = miopen::deref(inputTensor).GetSize() - 2; + const auto num_dim = miopen::deref(inputTensor).GetNumDims() - 2; if(num_dim != 2 && num_dim != 3) { printf("stats: for conv%dd\n", num_dim); @@ -1778,12 +1874,12 @@ int ConvDriver::RunWarmupFindForwardGPU() warmup_wall_total.resume(wall_enabled); auto rc = miopenFindConvolutionForwardAlgorithm(GetHandle(), warmupInputTensor, - warmup_in_dev->GetMem(), + warmup_in.GetDevicePtr(), warmupWeightTensor, - warmup_wei_dev->GetMem(), + warmup_wei.GetDevicePtr(), warmupConvDesc, warmupOutputTensor, - warmup_out_dev->GetMem(), + warmup_out.GetDevicePtr(), 1, &find_count, &find_result, @@ -1833,12 +1929,12 @@ int ConvDriver::RunWarmupFindForwardGPU() warmup_wall_total.resume(wall_enabled); rc = miopenConvolutionForwardImmediate(handle, warmupWeightTensor, - warmup_wei_dev->GetMem(), + warmup_wei.GetDevicePtr(), warmupInputTensor, - warmup_in_dev->GetMem(), + warmup_in.GetDevicePtr(), warmupConvDesc, warmupOutputTensor, - warmup_out_dev->GetMem(), + warmup_out.GetDevicePtr(), nullptr, 0, solution.solution_id); @@ -1877,7 +1973,7 @@ int ConvDriver::RunForwardGPU() miopenTransformTensor(GetHandle(), &aph, inputTensor, - in_dev->GetMem(), + in.GetDevicePtr(), &bta, inputTensor_vect4, in_vect4_dev->GetMem()); @@ -1885,7 +1981,7 @@ int ConvDriver::RunForwardGPU() miopenTransformTensor(GetHandle(), &aph, weightTensor, - wei_dev->GetMem(), + wei.GetDevicePtr(), &bta, weightTensor_vect4, wei_vect4_dev->GetMem()); @@ -1908,10 +2004,10 @@ int ConvDriver::RunForwardGPU() miopenConvolutionForwardBias(GetHandle(), &alpha, biasTensor, - b_dev->GetMem(), + b.GetDevicePtr(), &beta, outputTensor, - out_dev->GetMem()); + out.GetDevicePtr()); if(time_enabled) { @@ -1924,16 +2020,17 @@ int ConvDriver::RunForwardGPU() bool is_int8 = data_type == miopenInt8 || data_type == miopenInt8x4; if(is_int8) - out_dev->FromGPU(GetStream(), out_int8.data()); + out.CopyFromDeviceToHost(GetStream(), out_int8); else - out_dev->FromGPU(GetStream(), out.data.data()); + out.CopyFromDeviceToHost(GetStream()); if(inflags.GetValueInt("dump_output")) { if(is_int8) dumpBufferToFile("dump_fwd_out_gpu.bin", out_int8.data(), out_int8.size()); else - dumpBufferToFile("dump_fwd_out_gpu.bin", out.data.data(), out.data.size()); + dumpBufferToFile( + "dump_fwd_out_gpu.bin", out.GetVectorData(), out.GetVectorSize()); } return rc; @@ -2002,12 +2099,9 @@ int ConvDriver::RunForwardGpuFind(const bool is_transform) // requested, -- so perf-db and find-db are fully updated. std::vector perf_results(request_algo_count); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif auto rc = FindForward(ret_algo_count, request_algo_count, perf_results, ctx); @@ -2030,9 +2124,9 @@ int ConvDriver::RunForwardGpuFind(const bool is_transform) wall.start(wall_enabled); auto in_tens = (is_transform ? inputTensor_vect4 : inputTensor); - auto in_buff = (is_transform ? in_vect4_dev->GetMem() : in_dev->GetMem()); + auto in_buff = (is_transform ? in_vect4_dev->GetMem() : in.GetDevicePtr()); auto wei_tens = (is_transform ? weightTensor_vect4 : weightTensor); - auto wei_buff = (is_transform ? wei_vect4_dev->GetMem() : wei_dev->GetMem()); + auto wei_buff = (is_transform ? wei_vect4_dev->GetMem() : wei.GetDevicePtr()); for(int i = 0; i < num_iterations; i++) { @@ -2046,7 +2140,7 @@ int ConvDriver::RunForwardGpuFind(const bool is_transform) algo, &beta, outputTensor, - out_dev->GetMem(), + out.GetDevicePtr(), workspace_dev != nullptr ? workspace_dev->GetMem() : nullptr, ws_size); if(rc != miopenStatusSuccess) @@ -2162,12 +2256,9 @@ int ConvDriver::RunForwardGpuImmed(const bool is_transform) if(rc != miopenStatusSuccess) return rc; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif auto ws = std::unique_ptr{ws_size > 0 ? new GPUMem{ctx, ws_size, 1} : nullptr}; @@ -2193,12 +2284,12 @@ int ConvDriver::RunForwardGpuImmed(const bool is_transform) rc = miopenConvolutionForwardImmediate( handle, (is_transform ? weightTensor_vect4 : weightTensor), - (is_transform ? wei_vect4_dev->GetMem() : wei_dev->GetMem()), + (is_transform ? wei_vect4_dev->GetMem() : wei.GetDevicePtr()), (is_transform ? inputTensor_vect4 : inputTensor), - (is_transform ? in_vect4_dev->GetMem() : in_dev->GetMem()), + (is_transform ? in_vect4_dev->GetMem() : in.GetDevicePtr()), convDesc, outputTensor, - out_dev->GetMem(), + out.GetDevicePtr(), ws ? ws->GetMem() : nullptr, ws_size, selected->solution_id); @@ -2247,8 +2338,8 @@ int ConvDriver::RunForwardCPU() { cpu_convolution_backward_data(miopen::deref(convDesc).GetSpatialDimension(), outhost, - wei, - in, + wei.GetTensor(), + in.GetTensor(), miopen::deref(convDesc).GetConvPads(), miopen::deref(convDesc).GetConvStrides(), miopen::deref(convDesc).GetConvDilations(), @@ -2256,14 +2347,14 @@ int ConvDriver::RunForwardCPU() if(inflags.GetValueInt("bias") != 0) { - cpu_bias_forward(outhost, b); + cpu_bias_forward(outhost, b.GetTensor()); } } else { cpu_convolution_forward(miopen::deref(convDesc).GetSpatialDimension(), - in, - wei, + in.GetTensor(), + wei.GetTensor(), outhost, miopen::deref(convDesc).GetConvPads(), miopen::deref(convDesc).GetConvStrides(), @@ -2275,7 +2366,7 @@ int ConvDriver::RunForwardCPU() outhost.par_for_each([&](auto out_n_id, auto out_k_id, auto... out_spatial_id_pack) { outhost(out_n_id, out_k_id, out_spatial_id_pack...) = double(outhost(out_n_id, out_k_id, out_spatial_id_pack...)) + - double(b.data[out_k_id]); + double(b.GetVector()[out_k_id]); }); } } @@ -2304,12 +2395,12 @@ int ConvDriver::RunForwardGPUReference() : miopen::solver::Id("ConvDirectNaiveConvFwd").Value(); auto rc = miopenConvolutionForwardImmediate(handle, weightTensor, - wei_dev->GetMem(), + wei.GetDevicePtr(), inputTensor, - in_dev->GetMem(), + in.GetDevicePtr(), convDesc, outputTensor, - out_dev->GetMem(), + out.GetDevicePtr(), nullptr, 0, ref_solution_id); @@ -2322,14 +2413,17 @@ int ConvDriver::RunForwardGPUReference() if(miopen_type{} == miopen_type{} || miopen_type{} == miopenInt8 || miopen_type{} == miopenInt8x4) - out_dev->FromGPU(GetStream(), outhost.data.data()); + out.CopyFromDeviceToHost(GetStream(), outhost); else { - auto out_tmp = tensor(miopen::deref(outputTensor)); - out_dev->FromGPU(GetStream(), out_tmp.data.data()); - for(int i = 0; i < out_tmp.data.size(); i++) + if(!is_gpualloc) { - outhost.data[i] = static_cast(out_tmp.data[i]); + auto out_tmp = tensor(miopen::deref(outputTensor)); + out.CopyFromDeviceToHost(GetStream(), out_tmp); + for(size_t i = 0; i < out_tmp.data.size(); ++i) + { + outhost.data[i] = static_cast(out_tmp.data[i]); + } } } @@ -2354,12 +2448,12 @@ int ConvDriver::FindBackwardData(int& ret_algo_count, const auto rc = miopenFindConvolutionBackwardDataAlgorithm( GetHandle(), outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), weightTensor, - wei_dev->GetMem(), + wei.GetDevicePtr(), convDesc, inputTensor, - din_dev->GetMem(), + din.GetDevicePtr(), request_algo_count, &ret_algo_count, perf_results.data(), @@ -2381,12 +2475,12 @@ int ConvDriver::FindBackwardWeights(int& ret_algo_count, const auto rc = miopenFindConvolutionBackwardWeightsAlgorithm( GetHandle(), outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), inputTensor, - in_dev->GetMem(), + in.GetDevicePtr(), convDesc, weightTensor, - dwei_dev->GetMem(), + dwei.GetDevicePtr(), request_algo_count, &ret_algo_count, perf_results.data(), @@ -2428,9 +2522,11 @@ int ConvDriver::RunBackwardGPU() if(inflags.GetValueInt("dump_output")) { if(is_bwd) - dumpBufferToFile("dump_bwd_din_gpu.bin", din.data(), din.size()); + dumpBufferToFile( + "dump_bwd_din_gpu.bin", din.GetVectorData(), din.GetVectorSize()); if(is_wrw) - dumpBufferToFile("dump_bwd_dwei_gpu.bin", dwei.data(), dwei.size()); + dumpBufferToFile( + "dump_bwd_dwei_gpu.bin", dwei.GetVectorData(), dwei.GetVectorSize()); } if(inflags.GetValueInt("bias") != 0) @@ -2440,10 +2536,10 @@ int ConvDriver::RunBackwardGPU() ret |= miopenConvolutionBackwardBias(GetHandle(), &alpha, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), &beta, biasTensor, - db_dev->GetMem()); + db.GetDevicePtr()); if(time_enabled) { @@ -2452,10 +2548,10 @@ int ConvDriver::RunBackwardGPU() printf("GPU Kernel Time Backward Bias Conv. Elapsed: %f ms\n", time); } - db_dev->FromGPU(GetStream(), db.data()); + db.CopyFromDeviceToHost(GetStream()); if(inflags.GetValueInt("dump_output")) { - dumpBufferToFile("dump_bwd_db_gpu.bin", db.data(), db.size()); + dumpBufferToFile("dump_bwd_db_gpu.bin", db.GetVectorData(), db.GetVectorSize()); } } return ret; @@ -2468,12 +2564,9 @@ int ConvDriver::RunBackwardDataGpuFind() int request_algo_count = 2; std::vector perf_results_data(request_algo_count); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif auto rc = FindBackwardData(ret_algo_count, request_algo_count, perf_results_data, ctx); @@ -2499,14 +2592,14 @@ int ConvDriver::RunBackwardDataGpuFind() rc = miopenConvolutionBackwardData(GetHandle(), &alpha, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), weightTensor, - wei_dev->GetMem(), + wei.GetDevicePtr(), convDesc, algo, &beta, inputTensor, - din_dev->GetMem(), + din.GetDevicePtr(), workspace_dev != nullptr ? workspace_dev->GetMem() : nullptr, ws_size); @@ -2547,7 +2640,7 @@ int ConvDriver::RunBackwardDataGpuFind() PrintBackwardDataTime(kernel_total_time, kernel_first_time); } - din_dev->FromGPU(GetStream(), din.data()); + din.CopyFromDeviceToHost(GetStream()); return rc; } @@ -2560,7 +2653,7 @@ void ConvDriver::PrintBackwardDataTime(float kernel_total_time, floa printf("GPU Kernel Time Backward Data Conv. Elapsed: %f ms (average)\n", kernel_average_time); - const auto num_dim = miopen::deref(inputTensor).GetSize() - 2; + const auto num_dim = miopen::deref(inputTensor).GetNumDims() - 2; if(num_dim != 2 && num_dim != 3) { printf("stats: for conv%dd\n", num_dim); @@ -2675,12 +2768,9 @@ int ConvDriver::RunBackwardWrwGpuFind() float alpha = static_cast(1), beta = static_cast(0); std::vector perf_results_weights(request_algo_count); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif auto rc = FindBackwardWeights(ret_algo_count, request_algo_count, perf_results_weights, ctx); @@ -2706,14 +2796,14 @@ int ConvDriver::RunBackwardWrwGpuFind() rc = miopenConvolutionBackwardWeights(GetHandle(), &alpha, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), inputTensor, - in_dev->GetMem(), + in.GetDevicePtr(), convDesc, algo, &beta, weightTensor, - dwei_dev->GetMem(), + dwei.GetDevicePtr(), workspace_dev != nullptr ? workspace_dev->GetMem() : nullptr, ws_size); @@ -2754,7 +2844,7 @@ int ConvDriver::RunBackwardWrwGpuFind() PrintBackwardWrwTime(kernel_total_time, kernel_first_time); } - dwei_dev->FromGPU(GetStream(), dwei.data()); + dwei.CopyFromDeviceToHost(GetStream()); return rc; } @@ -2771,7 +2861,7 @@ void ConvDriver::PrintBackwardWrwTime(float kernel_total_time, float printf("GPU Kernel Time Backward Weights Conv. Elapsed: %f ms (average)\n", kernel_average_time); - const auto num_dim = miopen::deref(inputTensor).GetSize() - 2; + const auto num_dim = miopen::deref(inputTensor).GetNumDims() - 2; if(num_dim != 2 && num_dim != 3) { printf("stats: for conv%dd\n", num_dim); @@ -2838,7 +2928,7 @@ void ConvDriver::PrintBackwardWrwTime(float kernel_total_time, float "stats: name, n, c, do, ho, wo, z, x, y, k, flopCnt, bytesRead, bytesWritten, GFLOPs, " "GB/s, timeMs\n"); printf("stats: %s%dx%dx%du%d, %u, %u, %u, %u, %u, %u, %u, %u, %u, %zu, %zu, %zu, %.0f, " - "%.0f, %f\n ", + "%.0f, %f\n", "bwdw-conv", wei_d, wei_h, @@ -2923,12 +3013,9 @@ int ConvDriver::RunBackwardDataGpuImmed() if(rc != miopenStatusSuccess) return rc; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif auto ws = std::unique_ptr{ws_size > 0 ? new GPUMem{ctx, ws_size, 1} : nullptr}; @@ -2947,12 +3034,12 @@ int ConvDriver::RunBackwardDataGpuImmed() { rc = miopenConvolutionBackwardDataImmediate(handle, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), weightTensor, - wei_dev->GetMem(), + wei.GetDevicePtr(), convDesc, inputTensor, - din_dev->GetMem(), + din.GetDevicePtr(), ws ? ws->GetMem() : nullptr, ws_size, selected->solution_id); @@ -2992,7 +3079,7 @@ int ConvDriver::RunBackwardDataGpuImmed() } is_bwd_igemm = (selected->algorithm == miopenConvolutionAlgoImplicitGEMM); - din_dev->FromGPU(GetStream(), din.data()); + din.CopyFromDeviceToHost(GetStream()); return rc; } @@ -3056,12 +3143,9 @@ int ConvDriver::RunBackwardWrwGpuImmed() if(rc != miopenStatusSuccess) return rc; + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif auto ws = std::unique_ptr{ws_size > 0 ? new GPUMem{ctx, ws_size, 1} : nullptr}; @@ -3080,12 +3164,12 @@ int ConvDriver::RunBackwardWrwGpuImmed() { rc = miopenConvolutionBackwardWeightsImmediate(handle, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), inputTensor, - in_dev->GetMem(), + in.GetDevicePtr(), convDesc, weightTensor, - dwei_dev->GetMem(), + dwei.GetDevicePtr(), ws ? ws->GetMem() : nullptr, ws_size, selected->solution_id); @@ -3126,7 +3210,7 @@ int ConvDriver::RunBackwardWrwGpuImmed() is_wrw_winograd = (selected->algorithm == miopenConvolutionAlgoWinograd); is_wrw_igemm = (selected->algorithm == miopenConvolutionAlgoImplicitGEMM); - dwei_dev->FromGPU(GetStream(), dwei.data()); + dwei.CopyFromDeviceToHost(GetStream()); return rc; } @@ -3136,9 +3220,9 @@ int ConvDriver::RunBackwardWeightsCPU() if(mode == miopenTranspose) { cpu_convolution_backward_weight(miopen::deref(convDesc).GetSpatialDimension(), - dout, + dout.GetTensor(), dwei_host, - in, + in.GetTensor(), miopen::deref(convDesc).GetConvPads(), miopen::deref(convDesc).GetConvStrides(), miopen::deref(convDesc).GetConvDilations(), @@ -3147,9 +3231,9 @@ int ConvDriver::RunBackwardWeightsCPU() else { cpu_convolution_backward_weight(miopen::deref(convDesc).GetSpatialDimension(), - in, + in.GetTensor(), dwei_host, - dout, + dout.GetTensor(), miopen::deref(convDesc).GetConvPads(), miopen::deref(convDesc).GetConvStrides(), miopen::deref(convDesc).GetConvDilations(), @@ -3172,8 +3256,8 @@ int ConvDriver::RunBackwardDataCPU() if(mode == miopenTranspose) { cpu_convolution_forward(miopen::deref(convDesc).GetSpatialDimension(), - dout, - wei, + dout.GetTensor(), + wei.GetTensor(), din_host, miopen::deref(convDesc).GetConvPads(), miopen::deref(convDesc).GetConvStrides(), @@ -3184,8 +3268,8 @@ int ConvDriver::RunBackwardDataCPU() { cpu_convolution_backward_data(miopen::deref(convDesc).GetSpatialDimension(), din_host, - wei, - dout, + wei.GetTensor(), + dout.GetTensor(), miopen::deref(convDesc).GetConvPads(), miopen::deref(convDesc).GetConvStrides(), miopen::deref(convDesc).GetConvDilations(), @@ -3204,7 +3288,7 @@ int ConvDriver::RunBackwardDataCPU() template int ConvDriver::RunBackwardBiasCPU() { - cpu_bias_backward_data(dout, db_host); + cpu_bias_backward_data(dout.GetTensor(), db_host); if(inflags.GetValueInt("dump_output")) { @@ -3223,12 +3307,12 @@ int ConvDriver::RunBackwardWeightsGPUReference() auto ref_solution_id = miopen::solver::Id("ConvDirectNaiveConvWrw").Value(); auto rc = miopenConvolutionBackwardWeightsImmediate(handle, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), inputTensor, - in_dev->GetMem(), + in.GetDevicePtr(), convDesc, weightTensor, - dwei_dev->GetMem(), + dwei.GetDevicePtr(), nullptr, 0, ref_solution_id); @@ -3240,14 +3324,17 @@ int ConvDriver::RunBackwardWeightsGPUReference() } if(miopen_type{} == miopen_type{}) - dwei_dev->FromGPU(GetStream(), dwei_host.data.data()); + dwei.CopyFromDeviceToHost(GetStream(), dwei_host); else { - auto dwei_tmp = tensor(miopen::deref(weightTensor)); - dwei_dev->FromGPU(GetStream(), dwei_tmp.data.data()); - for(int i = 0; i < dwei_tmp.data.size(); i++) + if(!is_gpualloc) { - dwei_host.data[i] = static_cast(dwei_tmp.data[i]); + auto dwei_tmp = tensor(miopen::deref(weightTensor)); + dwei.CopyFromDeviceToHost(GetStream(), dwei_tmp); + for(size_t i = 0; i < dwei_tmp.data.size(); ++i) + { + dwei_host.data[i] = static_cast(dwei_tmp.data[i]); + } } } @@ -3271,12 +3358,12 @@ int ConvDriver::RunBackwardDataGPUReference() : miopen::solver::Id("ConvDirectNaiveConvBwd").Value(); auto rc = miopenConvolutionBackwardDataImmediate(handle, outputTensor, - dout_dev->GetMem(), + dout.GetDevicePtr(), weightTensor, - wei_dev->GetMem(), + wei.GetDevicePtr(), convDesc, inputTensor, - din_dev->GetMem(), + din.GetDevicePtr(), nullptr, 0, ref_solution_id); @@ -3288,14 +3375,17 @@ int ConvDriver::RunBackwardDataGPUReference() } if(miopen_type{} == miopen_type{}) - din_dev->FromGPU(GetStream(), din_host.data.data()); + din.CopyFromDeviceToHost(GetStream(), din_host); else { - auto din_tmp = tensor(miopen::deref(inputTensor)); - din_dev->FromGPU(GetStream(), din_tmp.data.data()); - for(int i = 0; i < din_tmp.data.size(); i++) + if(!is_gpualloc) { - din_host.data[i] = static_cast(din_tmp.data[i]); + auto din_tmp = tensor(miopen::deref(inputTensor)); + din.CopyFromDeviceToHost(GetStream(), din_tmp); + for(size_t i = 0; i < din_tmp.data.size(); ++i) + { + din_host.data[i] = static_cast(din_tmp.data[i]); + } } } @@ -3348,6 +3438,10 @@ std::string ConvDriver::GetVerificationCacheFileName( { return "int8"; } + if(std::is_same::value) + { + return "int32"; + } else if(std::is_same::value) { return "float16"; @@ -3435,6 +3529,8 @@ int ConvDriver::VerifyForward() if(!is_fwd) return 0; + MIOPEN_THROW_IF(is_gpualloc, "'-G 1' and '-V 1' are incompatible"); + if(!is_fwd_run_failed) if(!TryReadVerificationCache(Direction::Fwd, outputTensor, outhost.data.data())) { @@ -3447,7 +3543,7 @@ int ConvDriver::VerifyForward() const auto isInt8 = (data_type == miopenInt8 || data_type == miopenInt8x4); auto error = is_fwd_run_failed ? std::numeric_limits::max() : (isInt8 ? miopen::rms_range(outhost.data, out_int8) - : miopen::rms_range(outhost.data, out.data)); + : miopen::rms_range(outhost.data, out.GetVector())); auto tolerance = GetDefaultTolerance(); // iGemm's deviation is higher than other algorithms. @@ -3479,6 +3575,8 @@ int ConvDriver::VerifyBackward() if(!(is_bwd || is_wrw)) return 0; + MIOPEN_THROW_IF(is_gpualloc, "'-G 1' and '-V 1' are incompatible"); + int cumulative_rc = 0; if(is_bwd) { @@ -3492,7 +3590,7 @@ int ConvDriver::VerifyBackward() } auto error_data = is_bwd_run_failed ? std::numeric_limits::max() - : miopen::rms_range(din_host.data, din); + : miopen::rms_range(din_host.data, din.GetVector()); auto tolerance = GetDefaultTolerance(); // iGemm's deviation is higher than other algorithms. @@ -3553,8 +3651,9 @@ int ConvDriver::VerifyBackward() if(std::is_same::value) tolerance = tolerance * 2; - auto error_weights = is_wrw_run_failed ? std::numeric_limits::max() - : miopen::rms_range(dwei_host.data, dwei); + auto error_weights = is_wrw_run_failed + ? std::numeric_limits::max() + : miopen::rms_range(dwei_host.data, dwei.GetVector()); if(!std::isfinite(error_weights) || error_weights > tolerance) { @@ -3577,7 +3676,7 @@ int ConvDriver::VerifyBackward() RunBackwardBiasCPU(); } - auto error_bias = miopen::rms_range(db_host.data, db); + auto error_bias = miopen::rms_range(db_host.data, db.GetVector()); const auto tolerance = GetDefaultTolerance(); if(!std::isfinite(error_bias) || error_bias > tolerance) { diff --git a/driver/ctc_driver.hpp b/driver/ctc_driver.hpp index 85ec754f92..c7fa9f02e6 100644 --- a/driver/ctc_driver.hpp +++ b/driver/ctc_driver.hpp @@ -26,23 +26,28 @@ #pragma once #include "InputFlags.hpp" +#include "ctc_verify.hpp" #include "driver.hpp" -#include "timer.hpp" #include "random.hpp" -#include "ctc_verify.hpp" +#include "timer.hpp" +#include "util_driver.hpp" +#include "util_file.hpp" + +#include +#include + #include <../test/verify.hpp> + #include +#include +#include #include #include -#include #include #include -#include -#include #include #include #include -#include template class CTCDriver : public Driver @@ -246,7 +251,7 @@ template int CTCDriver::AllocateBuffersAndCopy() { size_t probs_sz = batch_size * (num_class + 1) * max_time_step; - size_t labels_sz = std::accumulate(labelLengths.begin(), labelLengths.end(), 0); + size_t labels_sz = std::accumulate(labelLengths.begin(), labelLengths.end(), 0ULL); size_t workSpaceSize; size_t workSpaceSizeCPU; @@ -282,12 +287,9 @@ int CTCDriver::AllocateBuffersAndCopy() ctc_algo, &workSpaceSizeCPU); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif probs_dev = std::unique_ptr(new GPUMem(ctx, probs_sz, sizeof(Tgpu))); @@ -330,19 +332,13 @@ int CTCDriver::AllocateBuffersAndCopy() dumpBufferToFile("dump_inputLengths.bin", inputLengths.data(), batch_size); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP -#define CL_SUCCESS 0 - int status; -#endif - + status_t status; status = probs_dev->ToGPU(q, probs.data()); status |= losses_dev->ToGPU(q, losses.data()); status |= gradients_dev->ToGPU(q, gradients.data()); status |= workspace_dev->ToGPU(q, workspace.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/dm_activ.cpp b/driver/dm_activ.cpp new file mode 100644 index 0000000000..4828df9b35 --- /dev/null +++ b/driver/dm_activ.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "activ_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "activ") + return new ActivationDriver(); + if(base_arg == "activfp16") + return new ActivationDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_argmax.cpp b/driver/dm_argmax.cpp new file mode 100644 index 0000000000..a6daa5cf94 --- /dev/null +++ b/driver/dm_argmax.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "argmax_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "argmax") + return new ArgmaxDriver(); + if(base_arg == "argmaxfp16") + return new ArgmaxDriver(); + if(base_arg == "argmaxbfp16") + return new ArgmaxDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_bnorm.cpp b/driver/dm_bnorm.cpp new file mode 100644 index 0000000000..c7bab90bb5 --- /dev/null +++ b/driver/dm_bnorm.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "bn_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "bnorm") + return new BatchNormDriver(); + if(base_arg == "bnormfp16") + return new BatchNormDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_cat.cpp b/driver/dm_cat.cpp new file mode 100644 index 0000000000..db3c3b2897 --- /dev/null +++ b/driver/dm_cat.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "cat_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "cat") + return new CatDriver(); + if(base_arg == "catfp16") + return new CatDriver(); + if(base_arg == "catbfp16") + return new CatDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/test/gtest/layernorm_test.cpp b/driver/dm_conv.cpp similarity index 76% rename from test/gtest/layernorm_test.cpp rename to driver/dm_conv.cpp index d60bfe963c..8a9439e4c0 100644 --- a/test/gtest/layernorm_test.cpp +++ b/driver/dm_conv.cpp @@ -2,7 +2,7 @@ * * MIT License * - * Copyright (c) 2023 Advanced Micro Devices, Inc. + * Copyright (c) 2024 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 @@ -23,16 +23,14 @@ * SOFTWARE. * *******************************************************************************/ -#include "layernorm_test.hpp" -#ifdef MIOPEN_BETA_API +#include "conv_driver.hpp" +#include "registry_driver_maker.hpp" -struct LayerNormSolverTestFloat : LayerNormSolverTest +static Driver* makeDriver(const std::string& base_arg) { -}; + if(base_arg == "conv") + return new ConvDriver(); + return nullptr; +} -TEST_P(LayerNormSolverTestFloat, LayerNormTestFw){}; - -INSTANTIATE_TEST_SUITE_P(LayerNormTestSet, - LayerNormSolverTestFloat, - testing::ValuesIn(LayerNormTestConfigs())); -#endif +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_convbfp16.cpp b/driver/dm_convbfp16.cpp new file mode 100644 index 0000000000..f58507c52d --- /dev/null +++ b/driver/dm_convbfp16.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "convbfp16") + return new ConvDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_convbfp8.cpp b/driver/dm_convbfp8.cpp new file mode 100644 index 0000000000..1356545f46 --- /dev/null +++ b/driver/dm_convbfp8.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "convbfp8") + return new ConvDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_convfp16.cpp b/driver/dm_convfp16.cpp new file mode 100644 index 0000000000..9d7618df96 --- /dev/null +++ b/driver/dm_convfp16.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "convfp16") + return new ConvDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_convfp8.cpp b/driver/dm_convfp8.cpp new file mode 100644 index 0000000000..ffc1b3742f --- /dev/null +++ b/driver/dm_convfp8.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "convfp8") + return new ConvDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_convint8.cpp b/driver/dm_convint8.cpp new file mode 100644 index 0000000000..ed9a979376 --- /dev/null +++ b/driver/dm_convint8.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "convint8") + return new ConvDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_dropout.cpp b/driver/dm_dropout.cpp new file mode 100644 index 0000000000..898339ab57 --- /dev/null +++ b/driver/dm_dropout.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "dropout_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "dropout") + return new DropoutDriver(); + if(base_arg == "dropoutfp16") + return new DropoutDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_fusion.cpp b/driver/dm_fusion.cpp new file mode 100644 index 0000000000..5cce834b57 --- /dev/null +++ b/driver/dm_fusion.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "CBAInferFusion_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "CBAInfer") + return new CBAInferFusionDriver(); + if(base_arg == "CBAInferfp16") + return new CBAInferFusionDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_gemm.cpp b/driver/dm_gemm.cpp new file mode 100644 index 0000000000..464da3de1a --- /dev/null +++ b/driver/dm_gemm.cpp @@ -0,0 +1,42 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "gemm_driver.hpp" +#include "registry_driver_maker.hpp" + +#include + +static Driver* makeDriver(const std::string& base_arg) +{ +#if MIOPEN_USE_GEMM + if(base_arg == "gemm") + return new GemmDriver(); + if(base_arg == "gemmfp16") + return new GemmDriver(); +#endif + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_groupnorm.cpp b/driver/dm_groupnorm.cpp new file mode 100644 index 0000000000..bdad632424 --- /dev/null +++ b/driver/dm_groupnorm.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "groupnorm_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "groupnorm") + return new GroupNormDriver(); + if(base_arg == "groupnormfp16") + return new GroupNormDriver(); + if(base_arg == "groupnormbfp16") + return new GroupNormDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_layernorm.cpp b/driver/dm_layernorm.cpp new file mode 100644 index 0000000000..e13f4eb114 --- /dev/null +++ b/driver/dm_layernorm.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "layernorm_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "layernorm") + return new LayerNormDriver(); + if(base_arg == "layernormfp16") + return new LayerNormDriver(); + if(base_arg == "layernormbfp16") + return new LayerNormDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_lrn.cpp b/driver/dm_lrn.cpp new file mode 100644 index 0000000000..b9fbdffaca --- /dev/null +++ b/driver/dm_lrn.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "lrn_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "lrn") + return new LRNDriver(); + if(base_arg == "lrnfp16") + return new LRNDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_pool.cpp b/driver/dm_pool.cpp new file mode 100644 index 0000000000..4d60561fd6 --- /dev/null +++ b/driver/dm_pool.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "pool_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "pool") + return new PoolDriver(); + if(base_arg == "poolfp16") + return new PoolDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_reduce.cpp b/driver/dm_reduce.cpp new file mode 100644 index 0000000000..250a8226b8 --- /dev/null +++ b/driver/dm_reduce.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "reduce_driver.hpp" +#include "registry_driver_maker.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "reduce") + return new ReduceDriver(); + if(base_arg == "reducefp16") + return new ReduceDriver(); + if(base_arg == "reducefp64") + return new ReduceDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_rnn.cpp b/driver/dm_rnn.cpp new file mode 100644 index 0000000000..fdd69301d8 --- /dev/null +++ b/driver/dm_rnn.cpp @@ -0,0 +1,46 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "ctc_driver.hpp" +#include "registry_driver_maker.hpp" +#include "rnn_driver.hpp" +#include "rnn_seq_driver.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "rnn_seq") + return new RNNSeqDriver(); + if(base_arg == "rnn_seqfp16") + return new RNNSeqDriver(); + if(base_arg == "rnn") + return new RNNDriver(); + if(base_arg == "rnnfp16") + return new RNNDriver(); + if(base_arg == "ctc") + return new CTCDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_softmax.cpp b/driver/dm_softmax.cpp new file mode 100644 index 0000000000..a11c7b6ee2 --- /dev/null +++ b/driver/dm_softmax.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "registry_driver_maker.hpp" +#include "softmax_driver.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "softmax") + return new SoftmaxDriver(); + if(base_arg == "softmaxfp16") + return new SoftmaxDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_sum.cpp b/driver/dm_sum.cpp new file mode 100644 index 0000000000..ef89f1feda --- /dev/null +++ b/driver/dm_sum.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "registry_driver_maker.hpp" +#include "sum_driver.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "sum") + return new SumDriver(); + if(base_arg == "sumfp16") + return new SumDriver(); + if(base_arg == "sumbfp16") + return new SumDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/dm_tensorop.cpp b/driver/dm_tensorop.cpp new file mode 100644 index 0000000000..bcc6501566 --- /dev/null +++ b/driver/dm_tensorop.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "registry_driver_maker.hpp" +#include "tensorop_driver.hpp" + +static Driver* makeDriver(const std::string& base_arg) +{ + if(base_arg == "tensorop") + return new TensorOpDriver(); + if(base_arg == "tensoropfp16") + return new TensorOpDriver(); + return nullptr; +} + +REGISTER_DRIVER_MAKER(makeDriver); diff --git a/driver/driver.hpp b/driver/driver.hpp index 4cfc2b544e..58916fa02e 100644 --- a/driver/driver.hpp +++ b/driver/driver.hpp @@ -26,12 +26,7 @@ #ifndef GUARD_MIOPEN_DRIVER_HPP #define GUARD_MIOPEN_DRIVER_HPP -#if !defined(_WIN32) #include -#else -#include -#endif - #include "random.hpp" #include "InputFlags.hpp" @@ -40,6 +35,7 @@ #include #include #include +#include #include #include using half = half_float::half; @@ -108,25 +104,51 @@ struct GPUMem GPUMem(){}; GPUMem(uint32_t ctx, size_t psz, size_t pdata_sz) : _ctx(ctx), sz(psz), data_sz(pdata_sz) { - hipMalloc(static_cast(&buf), data_sz * sz); + auto status = hipMalloc(static_cast(&buf), GetSize()); + if(status != hipSuccess) + MIOPEN_THROW_HIP_STATUS(status, + "[MIOpenDriver] hipMalloc " + std::to_string(GetSize())); + MIOPEN_LOG_CUSTOM(miopen::LoggingLevel::Info2, + "MIOpenDriver", + "hipMalloc " << GetSize() << " at " << buf << " Ok"); } int ToGPU(hipStream_t q, void* p) { _q = q; - return static_cast(hipMemcpy(buf, p, data_sz * sz, hipMemcpyHostToDevice)); + return static_cast(hipMemcpy(buf, p, GetSize(), hipMemcpyHostToDevice)); } int FromGPU(hipStream_t q, void* p) { hipDeviceSynchronize(); _q = q; - return static_cast(hipMemcpy(p, buf, data_sz * sz, hipMemcpyDeviceToHost)); + return static_cast(hipMemcpy(p, buf, GetSize(), hipMemcpyDeviceToHost)); } void* GetMem() { return buf; } size_t GetSize() { return sz * data_sz; } - ~GPUMem() { hipFree(buf); } + ~GPUMem() + { + size_t size = 0; + auto status = hipMemPtrGetInfo(buf, &size); + if(status != hipSuccess) + MIOPEN_LOG_CUSTOM(miopen::LoggingLevel::Warning, + "MIOpenDriver", + "hipMemPtrGetInfo at " << buf << ' ' + << miopen::HIPErrorMessage(status, "")); + status = hipFree(buf); + if(status != hipSuccess) + MIOPEN_LOG_CUSTOM(miopen::LoggingLevel::Error, + "MIOpenDriver", + "hipFree " << size << " at " << buf << ' ' + << miopen::HIPErrorMessage(status, "")); + else + MIOPEN_LOG_CUSTOM(miopen::LoggingLevel::Info2, + "MIOpenDriver", + "hipFree " << size << " at " << buf << " Ok"); + } + hipStream_t _q; // Place holder for opencl context uint32_t _ctx; void* buf; diff --git a/driver/dropout_driver.hpp b/driver/dropout_driver.hpp index a954030cf0..80be8fa785 100644 --- a/driver/dropout_driver.hpp +++ b/driver/dropout_driver.hpp @@ -28,22 +28,28 @@ #include "InputFlags.hpp" #include "driver.hpp" -#include "timer.hpp" #include "dropout_gpu_emulator.hpp" -#include +#include "tensor_driver.hpp" +#include "timer.hpp" +#include "util_driver.hpp" +#include "util_file.hpp" + #include <../test/verify.hpp> + +#include +#include +#include + #include +#include +#include #include #include -#include #include #include -#include -#include #include #include #include -#include template class DropoutDriver : public Driver @@ -218,12 +224,9 @@ int DropoutDriver::AllocateBuffersAndCopy() miopenDropoutGetStatesSize(GetHandle(), &statesSizeInBytes); size_t states_size = statesSizeInBytes / sizeof(prngStates); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif states_dev = std::unique_ptr(new GPUMem(ctx, states_size, sizeof(prngStates))); @@ -286,13 +289,7 @@ int DropoutDriver::AllocateBuffersAndCopy() dumpBufferToFile("dump_dout.bin", dout.data.data(), out_sz); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP -#define CL_SUCCESS 0 - int status; -#endif - + status_t status; status = in_dev->ToGPU(q, in.data.data()); status |= din_dev->ToGPU(q, din.data.data()); status |= out_dev->ToGPU(q, out.data.data()); @@ -308,7 +305,7 @@ int DropoutDriver::AllocateBuffersAndCopy() status |= reservespace_dev->ToGPU(q, reservespace.data()); } - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/dropout_gpu_emulator.hpp b/driver/dropout_gpu_emulator.hpp index dd29aed5e4..435d3cba55 100644 --- a/driver/dropout_gpu_emulator.hpp +++ b/driver/dropout_gpu_emulator.hpp @@ -46,9 +46,12 @@ #define ROCRAND_2POW32_INV (2.3283064e-10f) -float uniform_distribution_emu(size_t v) { return ROCRAND_2POW32_INV + (v * ROCRAND_2POW32_INV); } +static float uniform_distribution_emu(size_t v) +{ + return ROCRAND_2POW32_INV + (v * ROCRAND_2POW32_INV); +} -void xorwow_skipahead_emu( +static void xorwow_skipahead_emu( unsigned long long skp, prngStates* state, const unsigned int skipahead_mat[XORWOW_PRECALC_MATRICES_NUM][XORWOW_PRECALC_MATRICES_SZ]) @@ -97,10 +100,10 @@ void xorwow_skipahead_emu( std::copy(std::begin(xor_vec), std::end(xor_vec), p); } -void xorwow_lite_init_emu(prngStates* cur_state, - const unsigned long long seed, - const unsigned long long subsequence, - const unsigned long long offset) +static void xorwow_lite_init_emu(prngStates* cur_state, + const unsigned long long seed, + const unsigned long long subsequence, + const unsigned long long offset) { cur_state->x = 123456789; cur_state->y = 362436069; @@ -128,8 +131,8 @@ void xorwow_lite_init_emu(prngStates* cur_state, cur_state->d += static_cast(offset) * 362437; } -void InitKernelStateEmulator(std::vector& states, - const miopenDropoutDescriptor_t dropoutDesc) +static void InitKernelStateEmulator(std::vector& states, + const miopenDropoutDescriptor_t dropoutDesc) { size_t states_num = miopen::deref(dropoutDesc).stateSizeInBytes / sizeof(prngStates); size_t wk_grp_num = std::min(size_t(MAX_PRNG_STATE / 256), (states_num + 255) / 256); @@ -205,8 +208,8 @@ void RunDropoutForwardEmulator(miopenHandle_t handle, size_t rsvsp_offset = 0) { (void)noise_shape; - auto in_dim = miopen::deref(inputTensor).GetSize(); - auto out_dim = miopen::deref(outputTensor).GetSize(); + auto in_dim = miopen::deref(inputTensor).GetNumDims(); + auto out_dim = miopen::deref(outputTensor).GetNumDims(); if(in_dim != out_dim) { printf("CPU verification: Input/Output dimension does not match\n"); @@ -289,8 +292,8 @@ void RunDropoutBackwardEmulator(const miopenDropoutDescriptor_t dropoutDesc, size_t out_offset = 0, size_t rsvsp_offset = 0) { - auto in_dim = miopen::deref(inputTensor).GetSize(); - auto out_dim = miopen::deref(outputTensor).GetSize(); + auto in_dim = miopen::deref(inputTensor).GetNumDims(); + auto out_dim = miopen::deref(outputTensor).GetNumDims(); if(in_dim != out_dim) { printf("CPU verification: Input/Output dimension does not match\n"); diff --git a/driver/gemm_driver.hpp b/driver/gemm_driver.hpp index 12b615f405..85c27e1f95 100644 --- a/driver/gemm_driver.hpp +++ b/driver/gemm_driver.hpp @@ -26,18 +26,25 @@ #ifndef GUARD_MIOPEN_GEMM_DRIVER_HPP #define GUARD_MIOPEN_GEMM_DRIVER_HPP +#include + #if MIOPEN_USE_GEMM #include "InputFlags.hpp" #include "driver.hpp" +#include "random.hpp" +#include "util_driver.hpp" + +#include <../test/verify.hpp> + +#include +#include + #include #include #include #include -#include -#include #include #include -#include "random.hpp" #define GEMM_DRIVER_DEBUG 0 @@ -236,12 +243,10 @@ int GemmDriver::AllocateBuffersAndCopy() size_t a_sz = gemm_desc.m * gemm_desc.k + (gemm_desc.batch_count - 1) * gemm_desc.strideA; size_t b_sz = gemm_desc.k * gemm_desc.n + (gemm_desc.batch_count - 1) * gemm_desc.strideB; size_t c_sz = gemm_desc.m * gemm_desc.n + (gemm_desc.batch_count - 1) * gemm_desc.strideC; -#if MIOPEN_BACKEND_OPENCL - cl_context ctx; + DEFINE_CONTEXT(ctx); +#if MIOPEN_BACKEND_OPENCL clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif a_dev = std::unique_ptr(new GPUMem(ctx, a_sz, sizeof(T))); b_dev = std::unique_ptr(new GPUMem(ctx, b_sz, sizeof(T))); @@ -274,16 +279,12 @@ int GemmDriver::AllocateBuffersAndCopy() b[i] = prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); #endif } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif + status_t status; status = a_dev->ToGPU(q, a.data()); status |= b_dev->ToGPU(q, b.data()); status |= c_dev->ToGPU(q, c.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/gru_verify_gemm.hpp b/driver/gru_verify_gemm.hpp index ec9cdbfe0f..3b6573f566 100644 --- a/driver/gru_verify_gemm.hpp +++ b/driver/gru_verify_gemm.hpp @@ -3,10 +3,14 @@ #define ADNN_MM_TRANSPOSE 1 -#include -#include -#include #include "dropout_gpu_emulator.hpp" +#include "mloConvHost.hpp" // ADNN_mm_cpu + +#include <../test/rnn_util.hpp> + +#include +#include +#include template void RunGRUForwardGEMMCPUVerify(miopenHandle_t handle, diff --git a/driver/lrn_driver.hpp b/driver/lrn_driver.hpp index 10f00f6905..679dcfda5b 100644 --- a/driver/lrn_driver.hpp +++ b/driver/lrn_driver.hpp @@ -26,21 +26,25 @@ #ifndef GUARD_MIOPEN_LRN_DRIVER_HPP #define GUARD_MIOPEN_LRN_DRIVER_HPP -#include "../test/verify.hpp" #include "InputFlags.hpp" #include "driver.hpp" #include "mloNormHost.hpp" +#include "random.hpp" #include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" + +#include "../test/verify.hpp" + +#include +#include + #include #include #include #include -#include -#include #include #include -#include "random.hpp" template class LRNDriver : public Driver @@ -225,12 +229,10 @@ int LRNDriver::AllocateBuffersAndCopy() size_t workSpaceSize = 0; miopenLRNGetWorkSpaceSize(outputTensor, &workSpaceSize); size_t workSpaceNbVal = workSpaceSize / sizeof(Tgpu); -#if MIOPEN_BACKEND_OPENCL - cl_context ctx; + DEFINE_CONTEXT(ctx); +#if MIOPEN_BACKEND_OPENCL clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif in_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); out_dev = std::unique_ptr(new GPUMem(ctx, out_sz, sizeof(Tgpu))); @@ -274,11 +276,7 @@ int LRNDriver::AllocateBuffersAndCopy() dout[i] = Data_scale * prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif + status_t status; status = in_dev->ToGPU(q, in.data()); if(do_backward) { @@ -289,7 +287,7 @@ int LRNDriver::AllocateBuffersAndCopy() status = din_dev->ToGPU(q, din.data()); status |= dout_dev->ToGPU(q, dout.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/lstm_verify_gemm.hpp b/driver/lstm_verify_gemm.hpp index 4407ee4e82..bd052c6142 100644 --- a/driver/lstm_verify_gemm.hpp +++ b/driver/lstm_verify_gemm.hpp @@ -3,10 +3,14 @@ #define ADNN_MM_TRANSPOSE 1 -#include -#include -#include #include "dropout_gpu_emulator.hpp" +#include "mloConvHost.hpp" // ADNN_mm_cpu + +#include <../test/rnn_util.hpp> + +#include +#include +#include template void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, @@ -38,10 +42,10 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, bool hx_is_null = false, bool cx_is_null = false) { - int batch_n = sumvc(in_n); + size_t batch_n = sumvc(in_n); int numlayer = bidirection ? hy_d / 2 : hy_d; - int bacc, baccbi; // accumulation of batch + size_t bacc, baccbi; // accumulation of batch int bi = bidirection ? 2 : 1; int in_stride = in_h; @@ -141,8 +145,8 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, // forward emulator for(int li = 0; li < numlayer; li++) { - int hid_shift = li * batch_n * hy_stride; - int hx_shift = li * in_n.at(0) * h_stride; + size_t hid_shift = li * batch_n * hy_stride; + size_t hx_shift = li * in_n.at(0) * h_stride; // from input if(li == 0) @@ -215,8 +219,9 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, } else { - int wei_shift = (in_h + hy_h) * wei_stride + (li - 1) * (bi * hy_h + hy_h) * wei_stride; - int prelayer_shift = (li - 1) * batch_n * hy_stride + bi * 5 * hy_h; + size_t wei_shift = + (in_h + hy_h) * wei_stride + (li - 1) * (bi * hy_h + hy_h) * wei_stride; + size_t prelayer_shift = (li - 1) * batch_n * hy_stride + bi * 5 * hy_h; if(use_dropout) { auto dropout_states_tmp = dropout_states_host; @@ -260,7 +265,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, // from bias if(biased) { - int wei_shift_bias_temp = wei_shift_bias + li * 2 * wei_stride; + size_t wei_shift_bias_temp = wei_shift_bias + li * 2 * wei_stride; for(int bs = 0; bs < batch_n; bs++) { @@ -279,7 +284,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, for(int ti = 0; ti < seqLength; ti++) { baccbi -= in_n.at(seqLength - 1 - ti); - int wei_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; + size_t wei_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; if(ti == 0) { @@ -306,7 +311,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, // from bias if(biased) { - int wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; + size_t wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; for(int bs = 0; bs < in_n[ti]; bs++) { @@ -379,7 +384,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, // from bias if(biased) { - int wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; + size_t wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; for(int bs = 0; bs < in_n[ti]; bs++) { @@ -419,7 +424,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, // from bias if(biased) { - int wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; + size_t wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; for(int bs = in_n.at(seqLength - ti); bs < in_n.at(seqLength - 1 - ti); bs++) @@ -455,7 +460,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, // from bias if(biased) { - int wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; + size_t wei_shift_bias_temp = wei_shift_bias + (li * 2 + 1) * wei_stride; for(int bs = 0; bs < in_n.at(seqLength - ti); bs++) { @@ -491,8 +496,8 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, } else { - int prec_shift = li * batch_n * hy_stride + - (bacc - in_n.at(ti - 1)) * hy_stride + bi * 4 * hy_h; + size_t prec_shift = li * batch_n * hy_stride + + (bacc - in_n.at(ti - 1)) * hy_stride + bi * 4 * hy_h; hid_state.at(hid_shift + (bacc + bs) * hy_stride + bi * 4 * hy_h + h) += activfunc(hid_state.at(hid_shift + (bacc + bs) * hy_stride + hy_h + h), @@ -579,7 +584,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, if(bs < in_n.at(seqLength - ti)) { - int prec_shift = + size_t prec_shift = li * batch_n * hy_stride + (baccbi + in_n.at(seqLength - 1 - ti)) * hy_stride + bi * 4 * hy_h + hy_h; @@ -641,7 +646,7 @@ void RunLSTMForwardGEMMCPUVerify(miopenHandle_t handle, } // output - int prelayer_shift = (numlayer - 1) * batch_n * hy_stride + bi * 5 * hy_h; + size_t prelayer_shift = (numlayer - 1) * batch_n * hy_stride + bi * 5 * hy_h; for(int i = 0; i < numlayer * batch_n * hy_stride * 2; i++) { @@ -711,10 +716,10 @@ void RunLSTMBackwardDataGEMMCPUVerify( bool dhy_is_null = false, bool dcy_is_null = false) { - int batch_n = sumvc(in_n); + size_t batch_n = sumvc(in_n); int numlayer = bidirection ? hy_d / 2 : hy_d; - int bacc, baccbi; // accumulation of batch + size_t bacc, baccbi; // accumulation of batch int bi = bidirection ? 2 : 1; int in_stride = in_h; @@ -816,9 +821,9 @@ void RunLSTMBackwardDataGEMMCPUVerify( // bwd data emulator for(int li = numlayer - 1; li >= 0; li--) { - int wei_shift = (in_h + hy_h) * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; - int hid_shift = li * batch_n * hy_stride; - int hx_shift = li * in_n[0] * h_stride; + size_t wei_shift = (in_h + hy_h) * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; + size_t hid_shift = li * batch_n * hy_stride; + size_t hx_shift = li * in_n[0] * h_stride; if(li == numlayer - 1) { @@ -833,7 +838,7 @@ void RunLSTMBackwardDataGEMMCPUVerify( } else { - int prelayer_shift = (li + 1) * batch_n * hy_stride; + size_t prelayer_shift = (li + 1) * batch_n * hy_stride; ADNN_mm_cpu(&dh_state[prelayer_shift], hy_h * bi * 4, @@ -942,8 +947,8 @@ void RunLSTMBackwardDataGEMMCPUVerify( } } - int pretime_shift = li * batch_n * hy_stride + (bacc + in_n[ti]) * hy_stride; - int weitime_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; + size_t pretime_shift = li * batch_n * hy_stride + (bacc + in_n[ti]) * hy_stride; + size_t weitime_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; ADNN_mm_cpu(&dh_state[pretime_shift], hy_h * 4, @@ -999,7 +1004,7 @@ void RunLSTMBackwardDataGEMMCPUVerify( { if(bs < in_n[ti + 1]) { - int pretime_shift = + size_t pretime_shift = li * batch_n * hy_stride + (bacc + in_n[ti]) * hy_stride; dh_state[hid_shift + (bacc + bs) * hy_stride + bi * 4 * hy_h + h] += @@ -1030,7 +1035,7 @@ void RunLSTMBackwardDataGEMMCPUVerify( } else { - int pretime_shift = + size_t pretime_shift = li * batch_n * hy_stride + (bacc - in_n[ti - 1]) * hy_stride; dh_state[hid_shift + (bacc + bs) * hy_stride + hy_h + h] += @@ -1067,8 +1072,8 @@ void RunLSTMBackwardDataGEMMCPUVerify( { if(ti < seqLength - 1) { - int pretime_shift = li * batch_n * hy_stride + - (baccbi - in_n[seqLength - 2 - ti]) * hy_stride; + size_t pretime_shift = li * batch_n * hy_stride + + (baccbi - in_n[seqLength - 2 - ti]) * hy_stride; dh_state[hid_shift + (baccbi + bs) * hy_stride + bi * 4 * hy_h + hy_h + h] += @@ -1121,8 +1126,9 @@ void RunLSTMBackwardDataGEMMCPUVerify( if(bs < in_n[seqLength - ti]) { - int pretime_shift = li * batch_n * hy_stride + - (baccbi + in_n[seqLength - 1 - ti]) * hy_stride; + size_t pretime_shift = + li * batch_n * hy_stride + + (baccbi + in_n[seqLength - 1 - ti]) * hy_stride; dh_state[hid_shift + (baccbi + bs) * hy_stride + 5 * hy_h + h] += dh_state[hid_shift + (baccbi + bs) * hy_stride + bi * 4 * hy_h + @@ -1170,8 +1176,8 @@ void RunLSTMBackwardDataGEMMCPUVerify( } // dcx, dhx - int pretime_shift = li * batch_n * hy_stride; - int weitime_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; + size_t pretime_shift = li * batch_n * hy_stride; + size_t weitime_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; ADNN_mm_cpu(&dh_state[pretime_shift], hy_h * 4, @@ -1335,9 +1341,9 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, bool use_dropout, bool hx_is_null = false) { - int batch_n = sumvc(in_n); - int numlayer = bidirection ? hy_d / 2 : hy_d; - int bacc; // accumulation of batch + size_t batch_n = sumvc(in_n); + int numlayer = bidirection ? hy_d / 2 : hy_d; + size_t bacc; // accumulation of batch int bi = bidirection ? 2 : 1; int in_stride = in_h; @@ -1398,8 +1404,8 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, in_h = 0; } - int wei_shift_bias = (in_h + hy_h + (bi * hy_h + hy_h) * (numlayer - 1)) * wei_stride; - int wei_len = wei_shift_bias; + size_t wei_shift_bias = (in_h + hy_h + (bi * hy_h + hy_h) * (numlayer - 1)) * wei_stride; + int wei_len = wei_shift_bias; if(biased) { int in_bias = 2; @@ -1449,11 +1455,12 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, } else { - int prelayer_shift = + size_t prelayer_shift = use_dropout ? 2 * numlayer * batch_n * hy_stride + (li - 1) * batch_n * hy_h * bi : (li - 1) * batch_n * hy_stride + bi * hy_h * 5; - int hid_shift = li * batch_n * hy_stride; - int wei_shift = (in_h + hy_h) * wei_stride + (li - 1) * (bi * hy_h + hy_h) * wei_stride; + size_t hid_shift = li * batch_n * hy_stride; + size_t wei_shift = + (in_h + hy_h) * wei_stride + (li - 1) * (bi * hy_h + hy_h) * wei_stride; ADNN_mm_cpu(&wkspace_state[hid_shift], hy_h * bi * 4, @@ -1491,9 +1498,9 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, bacc = 0; for(int ti = 0; ti < seqLength; ti++) { - int hid_shift = li * batch_n * hy_stride + bacc * hy_stride; - int hx_shift = li * in_n[0] * h_stride; - int wei_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; + size_t hid_shift = li * batch_n * hy_stride + bacc * hy_stride; + size_t hx_shift = li * in_n[0] * h_stride; + size_t wei_shift = in_h * wei_stride + li * (bi * hy_h + hy_h) * wei_stride; int pretime_shift; // between time @@ -1521,7 +1528,7 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, if(biased) { - int bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; + size_t bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; for(int h = 0; h < hy_h * 4; h++) { @@ -1559,7 +1566,7 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, if(biased) { - int bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; + size_t bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; for(int h = 0; h < hy_h * 4; h++) { @@ -1598,7 +1605,7 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, if(biased) { - int bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; + size_t bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; for(int h = 0; h < hy_h * 4; h++) { @@ -1636,7 +1643,7 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, if(biased) { - int bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; + size_t bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; for(int h = 0; h < hy_h * 4; h++) { @@ -1672,7 +1679,7 @@ void RunLSTMBackwardWeightGEMMCPUVerify(std::vector& in, if(biased) { - int bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; + size_t bias_shift = wei_shift_bias + li * 2 * wei_stride + wei_stride; for(int h = 0; h < hy_h * 4; h++) { diff --git a/driver/main.cpp b/driver/main.cpp index e1c5a62d1d..6389cb2a84 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -23,32 +23,15 @@ * SOFTWARE. * *******************************************************************************/ -#include -#include - -#include "activ_driver.hpp" -#include "bn_driver.hpp" -#include "conv_driver.hpp" -#include "CBAInferFusion_driver.hpp" #include "driver.hpp" -#include "groupnorm_driver.hpp" -#include "gemm_driver.hpp" -#include "lrn_driver.hpp" -#include "pool_driver.hpp" -#include "softmax_driver.hpp" -#include "rnn_driver.hpp" -#include "rnn_seq_driver.hpp" -#include "ctc_driver.hpp" -#include "dropout_driver.hpp" -#include "tensorop_driver.hpp" -#include "reduce_driver.hpp" -#include "layernorm_driver.hpp" -#include "sum_driver.hpp" -#include "argmax_driver.hpp" -#include "cat_driver.hpp" +#include "registry_driver_maker.hpp" + #include #include +#include +#include + int main(int argc, char* argv[]) { @@ -69,198 +52,14 @@ int main(int argc, char* argv[]) std::cout << " " << argv[i]; std::cout << std::endl; - Driver* drv; - if(base_arg == "conv") - { - drv = new ConvDriver(); - } - else if(base_arg == "convfp16") - { - drv = new ConvDriver(); - } - else if(base_arg == "convbfp16") - { - drv = new ConvDriver(); - } - else if(base_arg == "convint8") - { - drv = new ConvDriver(); - } - else if(base_arg == "convfp8") - { - drv = new ConvDriver(); - } - else if(base_arg == "convbfp8") - { - drv = new ConvDriver(); - } - else if(base_arg == "CBAInfer") - { - drv = new CBAInferFusionDriver(); - } - else if(base_arg == "CBAInferfp16") - { - drv = new CBAInferFusionDriver(); - } - else if(base_arg == "pool") - { - drv = new PoolDriver(); - } - else if(base_arg == "poolfp16") - { - drv = new PoolDriver(); - } - else if(base_arg == "lrn") - { - drv = new LRNDriver(); - } - else if(base_arg == "lrnfp16") - { - drv = new LRNDriver(); - } - else if(base_arg == "activ") - { - drv = new ActivationDriver(); - } - else if(base_arg == "activfp16") - { - drv = new ActivationDriver(); - } - else if(base_arg == "softmax") - { - drv = new SoftmaxDriver(); - } - else if(base_arg == "softmaxfp16") - { - drv = new SoftmaxDriver(); - } -#if MIOPEN_USE_GEMM - else if(base_arg == "gemm") - { - drv = new GemmDriver(); - } - else if(base_arg == "gemmfp16") - { - drv = new GemmDriver(); - } -#endif - else if(base_arg == "bnorm") - { - drv = new BatchNormDriver(); - } - else if(base_arg == "bnormfp16") - { - drv = new BatchNormDriver(); - } - else if(base_arg == "rnn_seq") - { - drv = new RNNSeqDriver(); - } - else if(base_arg == "rnn_seqfp16") - { - drv = new RNNSeqDriver(); - } - else if(base_arg == "rnn") - { - drv = new RNNDriver(); - } - else if(base_arg == "rnnfp16") - { - drv = new RNNDriver(); - } - else if(base_arg == "ctc") - { - drv = new CTCDriver(); - } - else if(base_arg == "dropout") - { - drv = new DropoutDriver(); - } - else if(base_arg == "dropoutfp16") - { - drv = new DropoutDriver(); - } - else if(base_arg == "groupnorm") - { - drv = new GroupNormDriver(); - } - else if(base_arg == "groupnormfp16") - { - drv = new GroupNormDriver(); - } - else if(base_arg == "groupnormbfp16") - { - drv = new GroupNormDriver(); - } - else if(base_arg == "tensorop") - { - drv = new TensorOpDriver(); - } - else if(base_arg == "tensoropfp16") - { - drv = new TensorOpDriver(); - } - else if(base_arg == "reduce") - { - drv = new ReduceDriver(); - } - else if(base_arg == "reducefp16") - { - drv = new ReduceDriver(); - } - else if(base_arg == "reducefp64") - { - drv = new ReduceDriver(); - } - else if(base_arg == "layernorm") - { - drv = new LayerNormDriver(); - } - else if(base_arg == "layernormfp16") - { - drv = new LayerNormDriver(); - } - else if(base_arg == "layernormbfp16") - { - drv = new LayerNormDriver(); - } - else if(base_arg == "sum") - { - drv = new SumDriver(); - } - else if(base_arg == "sumfp16") - { - drv = new SumDriver(); - } - else if(base_arg == "sumbfp16") - { - drv = new SumDriver(); - } - else if(base_arg == "argmax") - { - drv = new ArgmaxDriver(); - } - else if(base_arg == "argmaxfp16") - { - drv = new ArgmaxDriver(); - } - else if(base_arg == "argmaxbfp16") - { - drv = new ArgmaxDriver(); - } - else if(base_arg == "cat") - { - drv = new CatDriver(); - } - else if(base_arg == "catfp16") - { - drv = new CatDriver(); - } - else if(base_arg == "catbfp16") + Driver* drv = nullptr; + for(auto f : rdm::GetRegistry()) { - drv = new CatDriver(); + drv = f(base_arg); + if(drv != nullptr) + break; } - else + if(drv == nullptr) { printf("Incorrect BaseArg\n"); exit(0); // NOLINT (concurrency-mt-unsafe) diff --git a/driver/miopen_ConvBatchNormActivHost.hpp b/driver/miopen_ConvBatchNormActivHost.hpp index 0aa472a340..d54173fe82 100644 --- a/driver/miopen_ConvBatchNormActivHost.hpp +++ b/driver/miopen_ConvBatchNormActivHost.hpp @@ -27,10 +27,15 @@ #ifndef MIO_CONV_BATCHNORM_ACTIV_HOST_H_ #define MIO_CONV_BATCHNORM_ACTIV_HOST_H_ -#include -#include +#include "mloNeuronHost.hpp" + +#include #include #include +#include + +#include +#include template int miopenBNSpatialFwdInferHost(miopenTensorDescriptor_t& inputTensor, diff --git a/driver/mloGroupNormHost.hpp b/driver/mloGroupNormHost.hpp index e89f389ec9..f8ba946a5e 100644 --- a/driver/mloGroupNormHost.hpp +++ b/driver/mloGroupNormHost.hpp @@ -26,6 +26,8 @@ #ifndef MLO_GROUPNORMHOST_H_ #define MLO_GROUPNORMHOST_H_ +#include + //////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////// diff --git a/driver/mloNeuronHost.hpp b/driver/mloNeuronHost.hpp index bb03b12959..19c349d5ff 100644 --- a/driver/mloNeuronHost.hpp +++ b/driver/mloNeuronHost.hpp @@ -36,7 +36,7 @@ #include #include -#include "miopen/float_equal.hpp" +#include //////////////////////////////////////////////////////////// // diff --git a/driver/mloSoftmaxHost.hpp b/driver/mloSoftmaxHost.hpp index 4d4b24522f..94a7e08054 100644 --- a/driver/mloSoftmaxHost.hpp +++ b/driver/mloSoftmaxHost.hpp @@ -25,6 +25,9 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef MLO_SOFTMAXHOST_H_ #define MLO_SOFTMAXHOST_H_ +#include +#include + //////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////// diff --git a/driver/pool_driver.hpp b/driver/pool_driver.hpp index bb3fa161e1..9d3bebeb51 100644 --- a/driver/pool_driver.hpp +++ b/driver/pool_driver.hpp @@ -28,19 +28,24 @@ #include "InputFlags.hpp" #include "driver.hpp" +#include "mloConvHost.hpp" #include "mloPoolingHost.hpp" +#include "random.hpp" #include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" +#include "util_file.hpp" + +#include +#include +#include + #include #include #include #include -#include -#include -#include #include #include -#include "random.hpp" template void dumpBufferToFile(const char* fileName, T* data, size_t dataNumItems); @@ -372,12 +377,9 @@ int PoolDriver_impl::AllocateBuffersAndCopy() workSpaceSize / sizeof(Index); // work space is used by mask_dev and mask which are of type Index + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif in_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); out_dev = std::unique_ptr(new GPUMem(ctx, out_sz, sizeof(Tgpu))); @@ -420,18 +422,14 @@ int PoolDriver_impl::AllocateBuffersAndCopy() dumpBufferToFile((dump_root + "/dump_dout.bin").c_str(), dout.data(), out_sz); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif + status_t status; status = in_dev->ToGPU(q, in.data()); status |= out_dev->ToGPU(q, out.data()); status = din_dev->ToGPU(q, din.data()); status |= dout_dev->ToGPU(q, dout.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/random.hpp b/driver/random.hpp index 8731471c62..538f0820a6 100644 --- a/driver/random.hpp +++ b/driver/random.hpp @@ -2,6 +2,8 @@ #define GUARD_RANDOM_GEN_ #include + +#include #include #include diff --git a/driver/reduce_driver.hpp b/driver/reduce_driver.hpp index d1161023df..5a1d6b0d71 100644 --- a/driver/reduce_driver.hpp +++ b/driver/reduce_driver.hpp @@ -26,31 +26,32 @@ #ifndef GUARD_MIOPEN_REDUCE_DRIVER_HPP #define GUARD_MIOPEN_REDUCE_DRIVER_HPP -#include "../test/verify.hpp" #include "InputFlags.hpp" #include "driver.hpp" +#include "miopen_Reduction.hpp" +#include "random.hpp" #include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" +#include "util_file.hpp" + +#include "../test/verify.hpp" + +#include +#include +#include + +#include + #include +#include #include #include #include -#include -#include -#include #include -#include #include -#include #include -#if !defined(_WIN32) -#include -#else -#include -#endif -#include "random.hpp" - -#include "miopen_Reduction.hpp" +#include template class ReduceDriver : public Driver @@ -321,12 +322,9 @@ int ReduceDriver::AllocateBuffersAndCopy() : this->ws_sizeInBytes / (sizeof(Tgpu) + sizeof(int)); size_t indices_nelem = this->indices_sizeInBytes / sizeof(int); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif in_dev = std::unique_ptr(new GPUMem(ctx, in_nelem, sizeof(Tgpu))); out_dev = std::unique_ptr(new GPUMem(ctx, out_nelem, sizeof(Tgpu))); @@ -356,15 +354,11 @@ int ReduceDriver::AllocateBuffersAndCopy() }; }; -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif + status_t status; status = in_dev->ToGPU(q, in.data()); status |= out_dev->ToGPU(q, out.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/registry_driver_maker.cpp b/driver/registry_driver_maker.cpp new file mode 100644 index 0000000000..9b6f537ffb --- /dev/null +++ b/driver/registry_driver_maker.cpp @@ -0,0 +1,54 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "registry_driver_maker.hpp" + +namespace rdm { + +namespace { + +std::vector& get_registry() +{ + static std::vector rv; + return rv; +} + +} // namespace + +// Public access is read-only. +const std::vector& GetRegistry() +{ + return const_cast&>(get_registry()); +} + +namespace impl { +bool Register(DriverMaker f) +{ + get_registry().emplace_back(f); + return true; +} +} // namespace impl + +} // namespace rdm diff --git a/driver/registry_driver_maker.hpp b/driver/registry_driver_maker.hpp new file mode 100644 index 0000000000..7299777f0a --- /dev/null +++ b/driver/registry_driver_maker.hpp @@ -0,0 +1,53 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#ifndef GUARD_DRIVER_REGISTRY_DRIVER_MAKER_HPP +#define GUARD_DRIVER_REGISTRY_DRIVER_MAKER_HPP + +#include "driver.hpp" + +#include +#include + +namespace rdm { + +/// The function of the DriverMaker type should behave as follows: +/// If \p base_arg matches the command-line driver name, then +/// instantiates the driver object and returns pointer to it. +/// Otherwise, returns nullptr. +using DriverMaker = Driver* (*)(const std::string& base_arg); + +const std::vector& GetRegistry(); + +namespace impl { +bool Register(DriverMaker f); +} // namespace impl + +} // namespace rdm + +/// Registers the function of the DriverMaker type. +#define REGISTER_DRIVER_MAKER(name) static bool CALL_ONCE##name = ::rdm::impl::Register(name) + +#endif // GUARD_DRIVER_REGISTRY_DRIVER_MAKER_HPP diff --git a/driver/rnn_driver.hpp b/driver/rnn_driver.hpp index 8fc8de27be..03da917e0a 100644 --- a/driver/rnn_driver.hpp +++ b/driver/rnn_driver.hpp @@ -27,29 +27,34 @@ #define GUARD_MIOPEN_RNN_DRIVER_HPP #include "InputFlags.hpp" -#include "rnn_verify_gemm.hpp" -#include "lstm_verify_gemm.hpp" -#include "gru_verify_gemm.hpp" #include "driver.hpp" +#include "gru_verify_gemm.hpp" +#include "lstm_verify_gemm.hpp" +#include "mloConvHost.hpp" +#include "random.hpp" +#include "rnn_verify_gemm.hpp" #include "tensor_driver.hpp" #include "timer.hpp" #include "util_driver.hpp" -#include "random.hpp" +#include "util_file.hpp" + #include <../test/verify.hpp> + +#include +#include +#include +#include + #include +#include +#include #include #include -#include #include #include -#include -#include -#include -#include #include #include #include -#include template class RNNDriver : public Driver @@ -481,12 +486,9 @@ int RNNDriver::SetRNNDescriptorFromCmdLineArgs() miopenDropoutGetStatesSize(GetHandle(), &statesSizeInBytes); size_t states_size = statesSizeInBytes / sizeof(prngStates); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif dropout_states_dev = @@ -564,12 +566,9 @@ int RNNDriver::AllocateBuffersAndCopy() workSpace_sz /= sizeof(Tgpu); reserveSpace_sz = (reserveSpace_sz + sizeof(Tgpu) - 1) / sizeof(Tgpu); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif in_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); @@ -619,7 +618,7 @@ int RNNDriver::AllocateBuffersAndCopy() int nseq = inflags.GetValueInt("seq_len"); std::vector in_n = GetInputTensorLengthsFromCmdLine(); std::size_t inputBatchLenSum; - inputBatchLenSum = std::accumulate(in_n.begin(), in_n.begin() + nseq, 0); + inputBatchLenSum = std::accumulate(in_n.begin(), in_n.begin() + nseq, 0ULL); int hid_h = inflags.GetValueInt("hid_h"); int layer = inflags.GetValueInt("num_layer"); @@ -733,13 +732,7 @@ int RNNDriver::AllocateBuffersAndCopy() dumpBufferToFile("dump_wei.bin", wei.data(), wei_sz); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP -#define CL_SUCCESS 0 - int status; -#endif - + status_t status; status = in_dev->ToGPU(q, in.data()); status |= wei_dev->ToGPU(q, wei.data()); status |= out_dev->ToGPU(q, out.data()); @@ -748,7 +741,7 @@ int RNNDriver::AllocateBuffersAndCopy() status |= workspace_dev->ToGPU(q, workspace.data()); status |= reservespace_dev->ToGPU(q, reservespace.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); if(inflags.GetValueInt("forw") != 2) @@ -756,7 +749,7 @@ int RNNDriver::AllocateBuffersAndCopy() status = hy_dev->ToGPU(q, hy.data()); status |= cy_dev->ToGPU(q, cy.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); } @@ -770,7 +763,7 @@ int RNNDriver::AllocateBuffersAndCopy() status |= dhy_dev->ToGPU(q, dhy.data()); status |= dcy_dev->ToGPU(q, dcy.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); } @@ -903,16 +896,6 @@ int RNNDriver::RunForwardGPU() return miopenStatusSuccess; } -std::tuple -GetTempPackedBuffersSize(std::vector batchs, int in_vec, int out_vec) -{ - size_t total_batch = std::accumulate(batchs.begin(), batchs.end(), 0); - - size_t in_buff_size = total_batch * in_vec; - size_t out_buff_size = total_batch * out_vec; - return {in_buff_size, out_buff_size}; -} - template void ChangeDataPadding(std::vector& src_array, std::vector& dst_array, diff --git a/driver/rnn_seq_driver.hpp b/driver/rnn_seq_driver.hpp index 616cfacd72..8d5b720960 100644 --- a/driver/rnn_seq_driver.hpp +++ b/driver/rnn_seq_driver.hpp @@ -25,37 +25,38 @@ *******************************************************************************/ #pragma once +#include "InputFlags.hpp" +#include "driver.hpp" +#include "gru_verify_gemm.hpp" +#include "lstm_verify_gemm.hpp" +#include "random.hpp" +#include "rnn_verify_gemm.hpp" +#include "tensor_driver.hpp" +#include "timer.hpp" +#include "util_driver.hpp" +#include "util_file.hpp" + +#include <../test/verify.hpp> + +#include +#include +#include +#include +#include +#include +#include + #include +#include +#include #include #include -#include #include -#include #include +#include #include #include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include <../test/verify.hpp> -//#include <../test/rnn_util.hpp> - -#include "InputFlags.hpp" -#include "rnn_verify_gemm.hpp" -#include "lstm_verify_gemm.hpp" -#include "gru_verify_gemm.hpp" -#include "driver.hpp" -#include "tensor_driver.hpp" -#include "timer.hpp" -#include "util_driver.hpp" -#include "random.hpp" std::vector get_default_time_strides(int vec_size, const std::vector& seq_array) { @@ -716,12 +717,9 @@ int RNNSeqDriver::SetRNNDescriptorFromCmdLineArgs() miopenDropoutGetStatesSize(GetHandle(), &statesSizeInBytes); size_t states_size = statesSizeInBytes / sizeof(prngStates); + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; - clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif dropout_states_dev = @@ -768,7 +766,7 @@ inline size_t Get3DNoVECTensorSize(miopenTensorDescriptor_t& tensor) assert(miopen::deref(tensor).IsPacked() && "GetTensorSize should not be used on an unpacked tensor."); const auto len = GetTensorLengths(tensor); - size_t sz = std::accumulate(len.begin(), len.end(), 1, std::multiplies()); + size_t sz = std::accumulate(len.begin(), len.end(), 1ULL, std::multiplies()); return sz; } @@ -829,7 +827,7 @@ int RNNSeqDriver::AllocateBuffersAndCopy() const std::vector out_lens = GetOutputTensorLengthsFromCmdLine(); const size_t vectors_cnt_host = - std::accumulate(sorted_seq_lens.begin(), sorted_seq_lens.end(), 0); + std::accumulate(sorted_seq_lens.begin(), sorted_seq_lens.end(), 0ULL); const size_t vectors_cnt_gpu = io_layout == miopenRNNDataSeqMajorNotPadded ? vectors_cnt_host : in_lens[0] * in_lens[1]; diff --git a/driver/rnn_verify_gemm.hpp b/driver/rnn_verify_gemm.hpp index 213fcf1e12..c31f51e1c7 100644 --- a/driver/rnn_verify_gemm.hpp +++ b/driver/rnn_verify_gemm.hpp @@ -3,54 +3,13 @@ #define ADNN_MM_TRANSPOSE 1 -#include -#include -#include #include "dropout_gpu_emulator.hpp" -int sumvc(std::vector& x) -{ - int sum = 0; - for(int i = 0; i < x.size(); i++) - { - sum += x[i]; - } - return sum; -} - -template -T activfunc(T x, int actvf) -{ - T alpha = static_cast(1), beta0 = static_cast(0), beta1 = static_cast(1); - if(actvf == 0) - { - // float y = 0; - // return std::max(x, y); - return (x > 0) ? x : x * beta0; - } - else if(actvf == 2) - { - return 1 / (1 + exp(-x)); - } +#include <../test/rnn_util.hpp> - // return tanh(x); - return alpha * tanh(beta1 * x); -} - -template -T dervactivfunc(T x, int actvf) -{ - if(actvf == 0) - { - return (x > 0 ? 1 : 0); - } - else if(actvf == 2) - { - return exp(-x) / (1 + exp(-x)) / (1 + exp(-x)); - } - - return 1 / cosh(x) / cosh(x); -} +#include +#include +#include template void RunRNNForwardGEMMCPUVerify(miopenHandle_t handle, diff --git a/driver/rocrand_wrapper.cpp b/driver/rocrand_wrapper.cpp new file mode 100644 index 0000000000..2a7d910c4b --- /dev/null +++ b/driver/rocrand_wrapper.cpp @@ -0,0 +1,59 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "rocrand_wrapper.hpp" + +#include + +namespace gpumemrand { + +namespace { +template +int gen_0_1_impl(T* buf, size_t sz) +{ + rocrand_cpp::xorwow_engine<> g; + rocrand_cpp::uniform_real_distribution d; + d(g, buf, sz); + return 0; +} +} // namespace + +int gen_0_1(double* buf, size_t sz) // +{ + return gen_0_1_impl(buf, sz); +} + +int gen_0_1(float* buf, size_t sz) // +{ + return gen_0_1_impl(buf, sz); +} + +int gen_0_1(half_float::half* buf, size_t sz) // +{ + return gen_0_1_impl(reinterpret_cast(buf), sz); +} + +} // namespace gpumemrand diff --git a/driver/rocrand_wrapper.hpp b/driver/rocrand_wrapper.hpp new file mode 100644 index 0000000000..fc680cf98c --- /dev/null +++ b/driver/rocrand_wrapper.hpp @@ -0,0 +1,61 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once +#ifndef GUARD_ROCRAND_WRAPPER_HPP +#define GUARD_ROCRAND_WRAPPER_HPP + +// The wrapper is necessary because direct inclusion of rocrand.hpp into the driver source leads to +// build errors like this: "amd_hip_fp16.h:1743:19: error: type alias redefinition with different +// types ('__half' vs 'half_float::half')." The reason is that the driver uses the definition of +// half from half.hpp, while rocrand uses definition of half type from HIP headers, i.e. the +// definitions are different. + +#include +#include + +#include +#include + +namespace gpumemrand { + +int gen_0_1(double* buf, size_t sz); +int gen_0_1(float* buf, size_t sz); +int gen_0_1(half_float::half* buf, size_t sz); + +template +int gen_0_1(T* buf, size_t sz) +{ + std::cout << "Warning: gpumemrand functions are supported only for double, float and half. GPU " + "buffer { " + << static_cast(buf) << ", " << sz << ", " + << boost::core::demangle(typeid(T).name()) << " } remains uninitialized." + << std::endl; + return 0; +} + +} // namespace gpumemrand + +#endif // GUARD_ROCRAND_WRAPPER_HPP diff --git a/driver/softmax_driver.hpp b/driver/softmax_driver.hpp index aa4502c13c..b683ff1ed3 100644 --- a/driver/softmax_driver.hpp +++ b/driver/softmax_driver.hpp @@ -29,18 +29,22 @@ #include "InputFlags.hpp" #include "driver.hpp" #include "mloSoftmaxHost.hpp" +#include "random.hpp" #include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" + #include <../test/verify.hpp> + +#include +#include + #include -#include #include +#include #include -#include -#include #include #include -#include "random.hpp" template class SoftmaxDriver : public Driver @@ -186,12 +190,10 @@ int SoftmaxDriver::AllocateBuffersAndCopy() size_t in_sz = GetTensorSize(inputTensor); size_t out_sz = GetTensorSize(outputTensor); -#if MIOPEN_BACKEND_OPENCL - cl_context ctx; + DEFINE_CONTEXT(ctx); +#if MIOPEN_BACKEND_OPENCL clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif in_dev = std::unique_ptr(new GPUMem(ctx, in_sz, sizeof(Tgpu))); out_dev = std::unique_ptr(new GPUMem(ctx, out_sz, sizeof(Tgpu))); @@ -218,18 +220,14 @@ int SoftmaxDriver::AllocateBuffersAndCopy() dout[i] = Data_scale * prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif + status_t status; status = in_dev->ToGPU(q, in.data()); status |= out_dev->ToGPU(q, out.data()); status |= din_dev->ToGPU(q, din.data()); status |= dout_dev->ToGPU(q, dout.data()); - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; @@ -240,6 +238,7 @@ int SoftmaxDriver::RunForwardGPU() { float kernel_total_time = 0.0; float kernel_first_time = 0.0; + float wall_first_time = 0.0; Timer t; START_TIME @@ -260,19 +259,36 @@ int SoftmaxDriver::RunForwardGPU() miopenGetKernelTime(GetHandle(), &time); kernel_total_time += time; if(i == 0) + { kernel_first_time = time; + STOP_TIME + wall_first_time = t.gettime_ms(); + START_TIME + } } if(inflags.GetValueInt("time") == 1) { STOP_TIME - int iter = inflags.GetValueInt("iter"); - if(WALL_CLOCK) - printf("Wall-clock Time Forward Softmax Elapsed: %f ms\n", t.gettime_ms() / iter); + int iter = inflags.GetValueInt("iter"); + auto gpu_time = kernel_first_time; + auto wall_time = wall_first_time; + auto aux_wall_time = 0.0f; + if(iter > 1) + { + gpu_time = (kernel_total_time - kernel_first_time) / (iter - 1); + wall_time = t.gettime_ms() / (iter - 1); + aux_wall_time = wall_first_time - wall_time; + } - float kernel_average_time = - iter > 1 ? (kernel_total_time - kernel_first_time) / (iter - 1) : kernel_first_time; - printf("GPU Kernel Time Forward Softmax Elapsed: %f ms\n", kernel_average_time); + if(WALL_CLOCK) + { + printf("Wall-clock Time Forward Softmax Elapsed: %f ms", wall_time); + if(iter > 1) + printf(", First Call Overhead: %f ms", aux_wall_time); + printf("\n"); + } + printf("GPU Kernel Time Forward Softmax Elapsed: %f ms\n", gpu_time); } out_dev->FromGPU(GetStream(), out.data()); @@ -291,6 +307,7 @@ int SoftmaxDriver::RunBackwardGPU() { float kernel_total_time = 0.0; float kernel_first_time = 0.0; + float wall_first_time = 0.0; Timer t; START_TIME @@ -313,19 +330,36 @@ int SoftmaxDriver::RunBackwardGPU() miopenGetKernelTime(GetHandle(), &time); kernel_total_time += time; if(i == 0) + { kernel_first_time = time; + STOP_TIME + wall_first_time = t.gettime_ms(); + START_TIME + } } if(inflags.GetValueInt("time") == 1) { STOP_TIME - int iter = inflags.GetValueInt("iter"); - if(WALL_CLOCK) - printf("Wall-clock Time Backward Softmax Elapsed: %f ms\n", t.gettime_ms() / iter); + int iter = inflags.GetValueInt("iter"); + auto gpu_time = kernel_first_time; + auto wall_time = wall_first_time; + auto aux_wall_time = 0.0f; + if(iter > 1) + { + gpu_time = (kernel_total_time - kernel_first_time) / (iter - 1); + wall_time = t.gettime_ms() / (iter - 1); + aux_wall_time = wall_first_time - wall_time; + } - float kernel_average_time = - iter > 1 ? (kernel_total_time - kernel_first_time) / (iter - 1) : kernel_first_time; - printf("GPU Kernel Time Backward Softmax Elapsed: %f ms\n", kernel_average_time); + if(WALL_CLOCK) + { + printf("Wall-clock Time Backward Softmax Elapsed: %f ms", wall_time); + if(iter > 1) + printf(", First Call Overhead: %f ms", aux_wall_time); + printf("\n"); + } + printf("GPU Kernel Time Backward Softmax Elapsed: %f ms\n", gpu_time); } din_dev->FromGPU(GetStream(), din.data()); diff --git a/driver/sum_driver.hpp b/driver/sum_driver.hpp index b348d3a22f..03589e29e6 100644 --- a/driver/sum_driver.hpp +++ b/driver/sum_driver.hpp @@ -58,7 +58,7 @@ int32_t mloSumForwardRunHost(miopenTensorDescriptor_t inputDesc, auto reduce_size = input_dims[dim]; auto output_numel = - std::accumulate(output_dims.begin(), output_dims.end(), 1L, std::multiplies()); + std::accumulate(output_dims.begin(), output_dims.end(), 1LL, std::multiplies()); auto inner_size = 1ULL; for(int32_t i = dim + 1; i < input_dims.size(); i++) diff --git a/driver/tensor_driver.hpp b/driver/tensor_driver.hpp index cb3139bf48..f6868fab98 100644 --- a/driver/tensor_driver.hpp +++ b/driver/tensor_driver.hpp @@ -101,7 +101,7 @@ inline std::vector GetTensorLengths(const miopenTensorDescriptor_t& tensor) } std::vector tensor_len; - tensor_len.resize(miopen::deref(tensor).GetSize()); + tensor_len.resize(miopen::deref(tensor).GetNumDims()); miopenGetTensorDescriptor(tensor, nullptr, tensor_len.data(), nullptr); return tensor_len; @@ -131,7 +131,7 @@ inline std::vector GetTensorStrides(const miopenTensorDescriptor_t& tensor) } std::vector tensor_strides; - tensor_strides.resize(miopen::deref(tensor).GetSize()); + tensor_strides.resize(miopen::deref(tensor).GetNumDims()); miopenGetTensorDescriptor(tensor, nullptr, nullptr, tensor_strides.data()); @@ -181,6 +181,14 @@ inline int SetTensorNd(miopenTensorDescriptor_t t, return miopenSetTensorDescriptor(t, data_type, len.size(), len.data(), strides.data()); } +inline int SetTensorNd(miopenTensorDescriptor_t t, + std::vector& len, + std::vector& strides, + miopenDataType_t data_type = miopenFloat) +{ + return miopenSetTensorDescriptorV2(t, data_type, len.size(), len.data(), strides.data()); +} + inline int SetTensorNd(miopenTensorDescriptor_t t, std::vector& len, const std::string& layout, @@ -208,10 +216,10 @@ inline int SetTensorNd(miopenTensorDescriptor_t t, return SetTensorNd(t, len, data_type); } - std::vector strides; - miopen::tensor_layout_to_strides(len, len_layout, layout, strides); - - return SetTensorNd(t, len, strides, data_type); + std::vector strides2; + std::vector len2(len.cbegin(), len.cend()); + miopen::tensor_layout_to_strides(len2, len_layout, layout, strides2); + return SetTensorNd(t, len2, strides2, data_type); } // This function ignores tensor strides completely and its result should not be interpreted as @@ -222,8 +230,8 @@ inline size_t GetTensorSize(const miopenTensorDescriptor_t& tensor) { assert(miopen::deref(tensor).IsPacked() && "GetTensorSize should not be used on an unpacked tensor."); - const auto len = GetTensorLengths(tensor); - const auto vectorLength = GetTensorVectorLength(tensor); + const auto len = GetTensorLengths(tensor); + const size_t vectorLength = GetTensorVectorLength(tensor); size_t sz = std::accumulate(len.begin(), len.end(), vectorLength, std::multiplies()); return sz; diff --git a/driver/tensorop_driver.hpp b/driver/tensorop_driver.hpp index f77d949d7c..34a84cd3b7 100644 --- a/driver/tensorop_driver.hpp +++ b/driver/tensorop_driver.hpp @@ -28,17 +28,15 @@ #include "InputFlags.hpp" #include "driver.hpp" -#include "tensor_driver.hpp" -#include "miopen/miopen.h" -#include "miopen/tensor.hpp" +#include "mloNeuronHost.hpp" #include "random.hpp" +#include "tensor_driver.hpp" #include "timer.hpp" +#include "util_driver.hpp" -#ifdef MIOPEN_BACKEND_HIP -#ifndef CL_SUCCESS -#define CL_SUCCESS 0 -#endif -#endif +#include +#include +#include template class TensorOpDriver : public Driver @@ -196,11 +194,9 @@ int TensorOpDriver::SetTensorOpFromCmdLineArgs() template int TensorOpDriver::AllocateBuffersAndCopy() { + DEFINE_CONTEXT(ctx); #if MIOPEN_BACKEND_OPENCL - cl_context ctx; clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx, nullptr); -#elif MIOPEN_BACKEND_HIP - uint32_t ctx = 0; #endif size_t sz = GetTensorSize(aTensor); @@ -235,12 +231,7 @@ int TensorOpDriver::AllocateBuffersAndCopy() } } -#if MIOPEN_BACKEND_OPENCL - cl_int status; -#elif MIOPEN_BACKEND_HIP - int status; -#endif - + status_t status; status = a_dev->ToGPU(q, a.data()); if(!is_set && !is_scale) { @@ -248,7 +239,7 @@ int TensorOpDriver::AllocateBuffersAndCopy() status |= c_dev->ToGPU(q, c.data()); } - if(status != CL_SUCCESS) + if(status != STATUS_SUCCESS) printf("Error copying data to GPU\n"); return miopenStatusSuccess; diff --git a/driver/util_driver.hpp b/driver/util_driver.hpp index a8249b8fbe..f51ce12732 100644 --- a/driver/util_driver.hpp +++ b/driver/util_driver.hpp @@ -26,6 +26,28 @@ #ifndef GUARD_MIOPEN_UTIL_DRIVER_HPP #define GUARD_MIOPEN_UTIL_DRIVER_HPP +#include +#include + +#include +#include + +#if MIOPEN_BACKEND_OPENCL +#define STATUS_SUCCESS CL_SUCCESS +typedef cl_int status_t; +typedef cl_context context_t; +#define DEFINE_CONTEXT(name) context_t name +typedef cl_command_queue stream; +#elif MIOPEN_BACKEND_HIP +#define STATUS_SUCCESS 0 +typedef int status_t; +typedef uint32_t context_t; +#define DEFINE_CONTEXT(name) context_t name = 0 +typedef hipStream_t stream; +#else // Unknown backend. +// No definitions -> build errors if used. +#endif + // Tin is data type of input data, // Tout is data type of output data template diff --git a/driver/util_file.hpp b/driver/util_file.hpp new file mode 100644 index 0000000000..edd069b9c1 --- /dev/null +++ b/driver/util_file.hpp @@ -0,0 +1,76 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#ifndef GUARD_DRIVER_UTIL_FILE_HPP +#define GUARD_DRIVER_UTIL_FILE_HPP + +#include +#include + +template +void dumpBufferToFile(const char* fileName, T* data, size_t dataNumItems) +{ + if(data == nullptr) + { + printf("Ignored ouput file %s - no host buffer to dump data from (nullptr)\n", fileName); + return; + } + std::ofstream outFile(fileName, std::ios::binary); + if(outFile) + { + outFile.write(reinterpret_cast(data), dataNumItems * sizeof(T)); + outFile.close(); + printf("Wrote output to file %s\n", fileName); + } + else + { + printf("Could not open file %s for writing\n", fileName); + } +} + +template +bool readBufferFromFile(T* data, size_t dataNumItems, const char* fileName) +{ + if(data == nullptr) + { + printf("Ignored input file %s - no host buffer to copy data into (nullptr)\n", fileName); + return false; + } + std::ifstream infile(fileName, std::ios::binary); + if(infile) + { + infile.read(reinterpret_cast(data), dataNumItems * sizeof(T)); + infile.close(); + printf("Read data from input file %s\n", fileName); + return true; + } + else + { + printf("Could not open file %s for reading\n", fileName); + return false; + } +} + +#endif // GUARD_DRIVER_UTIL_FILE_HPP diff --git a/driver/xorwow_skipahead_generator.hpp b/driver/xorwow_skipahead_generator.hpp index f9dc96ef85..d7d1fecd16 100644 --- a/driver/xorwow_skipahead_generator.hpp +++ b/driver/xorwow_skipahead_generator.hpp @@ -53,7 +53,7 @@ #define XORWOW_JUMP_LOG2_DEV 1 #define XORWOW_SEQUENCE_JUMP_LOG2 67 -unsigned int xorwow_next(prngStates* cur_state) +inline unsigned int xorwow_next(prngStates* cur_state) { const unsigned int t = cur_state->x ^ (cur_state->x >> 2); @@ -136,7 +136,7 @@ inline void mat_pow(unsigned int* matrixP, const unsigned int* matrix, unsigned } // Generate matrix one-step advanced -void skipahead_one_step(unsigned int* matrix) +inline void skipahead_one_step(unsigned int* matrix) { xorwowStates init_state; @@ -162,7 +162,7 @@ void skipahead_one_step(unsigned int* matrix) } // Generate (2^67)-step-ahead matrices -void generate_skipahead_matrices(unsigned int* matrix, bool is_skip_seq, bool is_device) +inline void generate_skipahead_matrices(unsigned int* matrix, bool is_skip_seq, bool is_device) { unsigned int matrixA[XORWOW_PRECALC_MATRICES_SZ]; unsigned int matrixB[XORWOW_PRECALC_MATRICES_SZ]; @@ -190,7 +190,7 @@ void generate_skipahead_matrices(unsigned int* matrix, bool is_skip_seq, bool is } // write macros in file -void write_macro(std::ofstream& os, bool is_device) +inline void write_macro(std::ofstream& os, bool is_device) { os << "#define XORWOW_DIM " << XORWOW_DIM << std::endl; os << "#define XORWOW_BITS " << XORWOW_BITS << std::endl; @@ -205,7 +205,8 @@ void write_macro(std::ofstream& os, bool is_device) } // write matrices in file -void write_mat(std::ofstream& os, const std::string name, unsigned int* matrix, bool is_device) +inline void +write_mat(std::ofstream& os, const std::string name, unsigned int* matrix, bool is_device) { os << "static " << (is_device ? "__constant " : "const ") << "unsigned int " << name << "[XORWOW_PRECALC_MATRICES_NUM][XORWOW_PRECALC_MATRICES_SZ] = {" << std::endl; @@ -224,7 +225,7 @@ void write_mat(std::ofstream& os, const std::string name, unsigned int* matrix, } // generate header files with precalculated skip-ahead matrices -void generate_skipahead_file() +inline void generate_skipahead_file() { static unsigned int skipahead_matrices[XORWOW_PRECALC_MATRICES_NUM][XORWOW_PRECALC_MATRICES_SZ]; static unsigned int skipahead_matrices_sequence[XORWOW_PRECALC_MATRICES_NUM] diff --git a/fin b/fin index b2cc1400f2..8c40a3c3b4 160000 --- a/fin +++ b/fin @@ -1 +1 @@ -Subproject commit b2cc1400f29ac380ec527aeb11c13e7352a481ce +Subproject commit 8c40a3c3b41a7d2fb31a8e747155fde4223919b9 diff --git a/include/miopen/config.h.in b/include/miopen/config.h.in index 847f4a24e1..d77fd7319f 100644 --- a/include/miopen/config.h.in +++ b/include/miopen/config.h.in @@ -106,6 +106,8 @@ const char* getOffloadBundlerBinPath(); #cmakedefine MIOPEN_CACHE_DIR "@MIOPEN_CACHE_DIR@" +#cmakedefine01 MIOPEN_USE_SQLITE_PERFDB + #define MIOPEN_USE_GEMM (MIOPEN_USE_ROCBLAS) // Usage of "defined" operator within macro expansion is undefined behavior, diff --git a/include/miopen/miopen.h b/include/miopen/miopen.h index f04b204bd5..89a3060c9e 100644 --- a/include/miopen/miopen.h +++ b/include/miopen/miopen.h @@ -346,6 +346,16 @@ MIOPEN_DECLARE_OBJECT(miopenDropoutDescriptor); */ MIOPEN_DECLARE_OBJECT(miopenReduceTensorDescriptor); +/*! @ingroup mha + * @brief Creates the miopenMhaDescriptor_t type + */ +MIOPEN_DECLARE_OBJECT(miopenMhaDescriptor); + +/*! @ingroup softmax + * @brief Creates the miopenSoftmaxDescriptor_t type + */ +MIOPEN_DECLARE_OBJECT(miopenSoftmaxDescriptor); + /*! @ingroup tensor * @enum miopenDataType_t * MIOpen floating point datatypes. Both 32-bit and 16-bit floats are supported in MIOpen. @@ -739,6 +749,16 @@ MIOPEN_EXPORT miopenStatus_t miopenSetTensorDescriptor(miopenTensorDescriptor_t const int* dimsA, const int* stridesA); +#ifdef MIOPEN_BETA_API +/*! @copydoc miopenSetTensorDescriptor() + */ +MIOPEN_EXPORT miopenStatus_t miopenSetTensorDescriptorV2(miopenTensorDescriptor_t tensorDesc, + miopenDataType_t dataType, + int nbDims, + const size_t* dimsA, + const size_t* stridesA); +#endif + #ifdef MIOPEN_BETA_API /*! @brief Set the tensor cast type * @@ -5305,15 +5325,55 @@ typedef enum miopenTensorConvolutionX = 1, miopenTensorConvolutionW = 2, miopenTensorConvolutionY = 3, + + miopenTensorMhaK = 4, + miopenTensorMhaQ = 5, + miopenTensorMhaV = 6, + miopenTensorMhaDescaleK = 7, + miopenTensorMhaDescaleQ = 8, + miopenTensorMhaDescaleV = 9, + miopenTensorMhaDescaleS = 10, + miopenTensorMhaScaleS = 11, + miopenTensorMhaScaleO = 12, + miopenTensorMhaDropoutProbability = 13, + miopenTensorMhaDropoutSeed = 14, + miopenTensorMhaDropoutOffset = 15, + miopenTensorMhaO = 16, + miopenTensorMhaAmaxO = 17, + miopenTensorMhaAmaxS = 18, + miopenTensorMhaM = 19, + miopenTensorMhaZInv = 20, + miopenTensorMhaDO = 21, + miopenTensorMhaDescaleO = 22, + miopenTensorMhaDescaleDO = 23, + miopenTensorMhaDescaleDS = 24, + miopenTensorMhaScaleDS = 25, + miopenTensorMhaScaleDQ = 26, + miopenTensorMhaScaleDK = 27, + miopenTensorMhaScaleDV = 28, + miopenTensorMhaDQ = 29, + miopenTensorMhaDK = 30, + miopenTensorMhaDV = 31, + miopenTensorMhaAmaxDQ = 32, + miopenTensorMhaAmaxDK = 33, + miopenTensorMhaAmaxDV = 34, + miopenTensorMhaAmaxDS = 35, + #ifdef MIOPEN_BETA_API - miopenTensorActivationX = 4, - miopenTensorActivationY = 5, - miopenTensorActivationDX = 6, - miopenTensorActivationDY = 7, - miopenTensorBiasX = 8, - miopenTensorBiasY = 9, - miopenTensorBias = 10, + miopenTensorActivationX = 36, + miopenTensorActivationY = 37, + miopenTensorActivationDX = 38, + miopenTensorActivationDY = 39, + miopenTensorBiasX = 40, + miopenTensorBiasY = 41, + miopenTensorBias = 42, + miopenTensorSoftmaxX = 43, + miopenTensorSoftmaxY = 44, + miopenTensorSoftmaxDX = 45, + miopenTensorSoftmaxDY = 46, #endif + + miopenTensorArgumentIsScalar = 1U << 31, } miopenTensorArgumentId_t; /*! @enum miopenTensorArgumentId_t @@ -5336,6 +5396,90 @@ MIOPEN_EXPORT miopenStatus_t miopenCreateConvProblem(miopenProblem_t* problem, miopenConvolutionDescriptor_t operatorDesc, miopenProblemDirection_t direction); +/*! @brief Initializes a problem object describing a Mha operation. + * + * @param problem Pointer to the problem to initialize + * @param operatorDesc Descriptor of the operator to be used + * @param direction Direction of the operation + * @return miopenStatus_t + */ + +MIOPEN_EXPORT miopenStatus_t miopenCreateMhaProblem(miopenProblem_t* problem, + miopenMhaDescriptor_t operatorDesc, + miopenProblemDirection_t direction); + +/*! @brief Creates the mha descriptor object + * + * @param mhaDesc Pointer to a mha descriptor type + * @return miopenStatus_t + */ + +MIOPEN_EXPORT miopenStatus_t miopenCreateMhaDescriptor(miopenMhaDescriptor_t* mhaDesc); + +/*! @brief Sets the Mha descriptor details + * + * Sets all of the descriptor details for the Mha + * + * @param mhaDesc Pointer to a Mha descriptor + * @param scale Scale + * @return miopenStatus_t + */ + +MIOPEN_EXPORT miopenStatus_t miopenSetMhaDescriptor(miopenMhaDescriptor_t mhaDesc, float scale); + +/*! @brief Gets the Mha descriptor details + * + * Retrieves all of the descriptor details for the Mha. + * + * @param mhaDesc Pointer to a Mha descriptor + * @param scale Scale (output) + * @return miopenStatus_t + */ + +MIOPEN_EXPORT miopenStatus_t miopenGetMhaDescriptor(miopenMhaDescriptor_t mhaDesc, float* scale); + +/*! @brief Creates the Softmax descriptor object + * + * @param softmaxDesc Pointer to an softmax descriptor type + * @return miopenStatus_t + */ + +MIOPEN_EXPORT miopenStatus_t miopenCreateSoftmaxDescriptor(miopenSoftmaxDescriptor_t* softmaxDesc); + +/*! @brief Sets the softmax descriptor details + * + * Sets all of the descriptor details for the softmax layer + * + * @param softmaxDesc Pointer to a softmax layer descriptor + * @param alpha Softmax alpha parameter + * @param beta Softmax beta parameter + * @param algorithm Softmax algorithm + * @param mode Softmax mode + * @return miopenStatus_t + */ +MIOPEN_EXPORT miopenStatus_t miopenSetSoftmaxDescriptor(miopenSoftmaxDescriptor_t softmaxDesc, + float alpha, + float beta, + miopenSoftmaxAlgorithm_t algorithm, + miopenSoftmaxMode_t mode); + +/*! @brief Gets the softmax layer descriptor details + * + * Retrieves all of the descriptor details for the softmax layer. + * + * @param softmaxDesc Pointer to a softmax layer descriptor (input) + * @param alpha Softmax alpha parameter (output) + * @param beta Softmax beta parameter (output) + * @param algorithm Softmax algorithm (output) + * @param mode Softmax mode (output) + * @return miopenStatus_t + */ +MIOPEN_EXPORT miopenStatus_t miopenGetSoftmaxDescriptor(const miopenSoftmaxDescriptor_t softmaxDesc, + float* alpha, + float* beta, + miopenSoftmaxAlgorithm_t* algorithm, + miopenSoftmaxMode_t* mode); + /*! @brief Destroys a problem object. * * @param problem Problem to destroy @@ -5445,7 +5589,7 @@ MIOPEN_EXPORT miopenStatus_t miopenFindSolutions(miopenHandle_t handle, size_t* numSolutions, size_t maxSolutions); -/*! @brief Values of a tensor argument for the miopenRunSolution function. +/*! @brief Values of a tensor or scalar argument for the miopenRunSolution function. */ struct miopenTensorArgument_t { @@ -5458,7 +5602,8 @@ struct miopenTensorArgument_t * is no way to tell from the API. Intended for the future use. */ miopenTensorDescriptor_t* descriptor; - /* @brief Pointer to the device memory buffer to use for the operation. + /* @brief Pointer to the device memory buffer to use for the operation or to the host memory if + * the value is scalar. */ void* buffer; }; @@ -5596,6 +5741,18 @@ MIOPEN_EXPORT miopenStatus_t miopenFuseProblems(miopenProblem_t problem1, miopen MIOPEN_EXPORT miopenStatus_t miopenCreateBiasProblem(miopenProblem_t* problem, miopenProblemDirection_t direction); +/*! @brief Initializes a problem object describing a softmax operation. + * + * @param problem Pointer to the problem to initialize + * @param operatorDesc Descriptor of the operator to be used + * @param direction Direction of the operation + * @return miopenStatus_t + */ + +MIOPEN_EXPORT miopenStatus_t miopenCreateSoftmaxProblem(miopenProblem_t* problem, + miopenSoftmaxDescriptor_t operatorDesc, + miopenProblemDirection_t direction); + #endif /** @} */ @@ -5730,6 +5887,726 @@ MIOPEN_EXPORT miopenStatus_t miopenGroupNormForward(miopenHandle_t handle, // CLOSEOUT groupnorm DOXYGEN GROUP #endif +#ifdef MIOPEN_BETA_API +// Graph API +/** @addtogroup GraphAPI + * + * @{ + */ + +/*! @brief Descriptor type + * + * An enumerated type that indicates the type of backend descriptors. Users create a backend + * descriptor of a particular type by passing a value from this enumerate to the + * miopenBackendCreateDescriptor() function. + */ +typedef enum +{ + MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR, + MIOPEN_BACKEND_ENGINE_DESCRIPTOR, + MIOPEN_BACKEND_ENGINECFG_DESCRIPTOR, + MIOPEN_BACKEND_ENGINEHEUR_DESCRIPTOR, + MIOPEN_BACKEND_EXECUTION_PLAN_DESCRIPTOR, + MIOPEN_BACKEND_INTERMEDIATE_INFO_DESCRIPTOR, + MIOPEN_BACKEND_KNOB_CHOICE_DESCRIPTOR, + MIOPEN_BACKEND_KNOB_INFO_DESCRIPTOR, + MIOPEN_BACKEND_LAYOUT_INFO_DESCRIPTOR, + MIOPEN_BACKEND_MATMUL_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_CONCAT_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_GEN_STATS_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_MATMUL_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_NORM_BACKWARD_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_NORM_FORWARD_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_POINTWISE_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_REDUCTION_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_RESAMPLE_BWD_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_RESAMPLE_FWD_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_RNG_DESCRIPTOR, + MIOPEN_BACKEND_OPERATION_SIGNAL_DESCRIPTOR, + MIOPEN_BACKEND_OPERATIONGRAPH_DESCRIPTOR, + MIOPEN_BACKEND_POINTWISE_DESCRIPTOR, + MIOPEN_BACKEND_REDUCTION_DESCRIPTOR, + MIOPEN_BACKEND_RESAMPLE_DESCRIPTOR, + MIOPEN_BACKEND_RNG_DESCRIPTOR, + MIOPEN_BACKEND_TENSOR_DESCRIPTOR, + MIOPEN_BACKEND_VARIANT_PACK_DESCRIPTOR, +} miopenBackendDescriptorType_t; + +/*! @brief Backend Descriptor's Attribute + * + * An enumerated type that indicates the backend descriptor attributes + * that can be set or get using miopenBackendSetAttribute() and miopenBackendGetAttribute() + * functions. The backend descriptor to which an attribute belongs is + * identified by the prefix of the attribute name. + */ +typedef enum +{ + MIOPEN_ATTR_POINTWISE_MODE = 0, + MIOPEN_ATTR_POINTWISE_MATH_PREC = 1, + MIOPEN_ATTR_POINTWISE_NAN_PROPAGATION = 2, + MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP = 3, + MIOPEN_ATTR_POINTWISE_RELU_UPPER_CLIP = 4, + MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP_SLOPE = 5, + MIOPEN_ATTR_POINTWISE_ELU_ALPHA = 6, + MIOPEN_ATTR_POINTWISE_SOFTPLUS_BETA = 7, + MIOPEN_ATTR_POINTWISE_SWISH_BETA = 8, + MIOPEN_ATTR_POINTWISE_AXIS = 9, + + MIOPEN_ATTR_CONVOLUTION_COMP_TYPE = 100, + MIOPEN_ATTR_CONVOLUTION_CONV_MODE = 101, + MIOPEN_ATTR_CONVOLUTION_DILATIONS = 102, + MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES = 103, + MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS = 104, + MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS = 105, + MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS = 106, + + MIOPEN_ATTR_ENGINEHEUR_MODE = 200, + MIOPEN_ATTR_ENGINEHEUR_OPERATION_GRAPH = 201, + MIOPEN_ATTR_ENGINEHEUR_RESULTS = 202, + MIOPEN_ATTR_ENGINEHEUR_SM_COUNT_TARGET = 203, + + MIOPEN_ATTR_ENGINECFG_ENGINE = 300, + MIOPEN_ATTR_ENGINECFG_INTERMEDIATE_INFO = 301, + MIOPEN_ATTR_ENGINECFG_KNOB_CHOICES = 302, + + MIOPEN_ATTR_EXECUTION_PLAN_HANDLE = 400, + MIOPEN_ATTR_EXECUTION_PLAN_ENGINE_CONFIG = 401, + MIOPEN_ATTR_EXECUTION_PLAN_WORKSPACE_SIZE = 402, + MIOPEN_ATTR_EXECUTION_PLAN_COMPUTED_INTERMEDIATE_UIDS = 403, + MIOPEN_ATTR_EXECUTION_PLAN_RUN_ONLY_INTERMEDIATE_UIDS = 404, + + MIOPEN_ATTR_INTERMEDIATE_INFO_UNIQUE_ID = 500, + MIOPEN_ATTR_INTERMEDIATE_INFO_SIZE = 501, + MIOPEN_ATTR_INTERMEDIATE_INFO_DEPENDENT_DATA_UIDS = 502, + MIOPEN_ATTR_INTERMEDIATE_INFO_DEPENDENT_ATTRIBUTES = 503, + + MIOPEN_ATTR_KNOB_CHOICE_KNOB_TYPE = 600, + MIOPEN_ATTR_KNOB_CHOICE_KNOB_VALUE = 601, + + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA = 700, + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA = 701, + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC = 702, + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W = 703, + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X = 704, + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y = 705, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA = 706, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA = 707, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC = 708, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W = 709, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX = 710, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY = 711, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA = 712, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA = 713, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC = 714, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW = 715, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X = 716, + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY = 717, + MIOPEN_ATTR_OPERATION_POINTWISE_PW_DESCRIPTOR = 750, + MIOPEN_ATTR_OPERATION_POINTWISE_XDESC = 751, + MIOPEN_ATTR_OPERATION_POINTWISE_BDESC = 752, + MIOPEN_ATTR_OPERATION_POINTWISE_YDESC = 753, + MIOPEN_ATTR_OPERATION_POINTWISE_ALPHA1 = 754, + MIOPEN_ATTR_OPERATION_POINTWISE_ALPHA2 = 755, + MIOPEN_ATTR_OPERATION_POINTWISE_DXDESC = 756, + MIOPEN_ATTR_OPERATION_POINTWISE_DYDESC = 757, + MIOPEN_ATTR_OPERATION_POINTWISE_TDESC = 758, + + MIOPEN_ATTR_OPERATION_GENSTATS_MODE = 770, + MIOPEN_ATTR_OPERATION_GENSTATS_MATH_PREC = 771, + MIOPEN_ATTR_OPERATION_GENSTATS_XDESC = 772, + MIOPEN_ATTR_OPERATION_GENSTATS_SUMDESC = 773, + MIOPEN_ATTR_OPERATION_GENSTATS_SQSUMDESC = 774, + + MIOPEN_ATTR_OPERATION_BN_FINALIZE_STATS_MODE = 780, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_MATH_PREC = 781, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_Y_SUM_DESC = 782, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_Y_SQ_SUM_DESC = 783, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_SCALE_DESC = 784, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_BIAS_DESC = 785, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_PREV_RUNNING_MEAN_DESC = 786, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_PREV_RUNNING_VAR_DESC = 787, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_UPDATED_RUNNING_MEAN_DESC = 788, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_UPDATED_RUNNING_VAR_DESC = 789, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_SAVED_MEAN_DESC = 790, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_SAVED_INV_STD_DESC = 791, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_EQ_SCALE_DESC = 792, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_EQ_BIAS_DESC = 793, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_ACCUM_COUNT_DESC = 794, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_EPSILON_DESC = 795, + MIOPEN_ATTR_OPERATION_BN_FINALIZE_EXP_AVERATE_FACTOR_DESC = 796, + + MIOPEN_ATTR_OPERATIONGRAPH_HANDLE = 800, + MIOPEN_ATTR_OPERATIONGRAPH_OPS = 801, + MIOPEN_ATTR_OPERATIONGRAPH_ENGINE_GLOBAL_COUNT = 802, + + MIOPEN_ATTR_TENSOR_BYTE_ALIGNMENT = 900, + MIOPEN_ATTR_TENSOR_DATA_TYPE = 901, + MIOPEN_ATTR_TENSOR_DIMENSIONS = 902, + MIOPEN_ATTR_TENSOR_STRIDES = 903, + MIOPEN_ATTR_TENSOR_VECTOR_COUNT = 904, + MIOPEN_ATTR_TENSOR_VECTORIZED_DIMENSION = 905, + MIOPEN_ATTR_TENSOR_UNIQUE_ID = 906, + MIOPEN_ATTR_TENSOR_IS_VIRTUAL = 907, + MIOPEN_ATTR_TENSOR_IS_BY_VALUE = 908, + MIOPEN_ATTR_TENSOR_REORDERING_MODE = 909, + MIOPEN_ATTR_TENSOR_RAGGED_OFFSET_DESC = 910, + + MIOPEN_ATTR_VARIANT_PACK_UNIQUE_IDS = 1000, + MIOPEN_ATTR_VARIANT_PACK_DATA_POINTERS = 1001, + MIOPEN_ATTR_VARIANT_PACK_INTERMEDIATES = 1002, + MIOPEN_ATTR_VARIANT_PACK_WORKSPACE = 1003, + + MIOPEN_ATTR_LAYOUT_INFO_TENSOR_UID = 1100, + MIOPEN_ATTR_LAYOUT_INFO_TYPES = 1101, + + MIOPEN_ATTR_KNOB_INFO_TYPE = 1200, + MIOPEN_ATTR_KNOB_INFO_MAXIMUM_VALUE = 1201, + MIOPEN_ATTR_KNOB_INFO_MINIMUM_VALUE = 1202, + MIOPEN_ATTR_KNOB_INFO_STRIDE = 1203, + + MIOPEN_ATTR_ENGINE_OPERATION_GRAPH = 1300, + MIOPEN_ATTR_ENGINE_GLOBAL_INDEX = 1301, + MIOPEN_ATTR_ENGINE_KNOB_INFO = 1302, + MIOPEN_ATTR_ENGINE_NUMERICAL_NOTE = 1303, + MIOPEN_ATTR_ENGINE_LAYOUT_INFO = 1304, + MIOPEN_ATTR_ENGINE_BEHAVIOR_NOTE = 1305, + MIOPEN_ATTR_ENGINE_SM_COUNT_TARGET = 1306, + + MIOPEN_ATTR_MATMUL_COMP_TYPE = 1500, + MIOPEN_ATTR_MATMUL_PADDING_VALUE = 1501, + + MIOPEN_ATTR_OPERATION_MATMUL_ADESC = 1520, + MIOPEN_ATTR_OPERATION_MATMUL_BDESC = 1521, + MIOPEN_ATTR_OPERATION_MATMUL_CDESC = 1522, + MIOPEN_ATTR_OPERATION_MATMUL_DESC = 1523, + MIOPEN_ATTR_OPERATION_MATMUL_IRREGULARLY_STRIDED_BATCH_COUNT = 1524, + MIOPEN_ATTR_OPERATION_MATMUL_GEMM_M_OVERRIDE_DESC = 1525, + MIOPEN_ATTR_OPERATION_MATMUL_GEMM_N_OVERRIDE_DESC = 1526, + MIOPEN_ATTR_OPERATION_MATMUL_GEMM_K_OVERRIDE_DESC = 1527, + + MIOPEN_ATTR_REDUCTION_OPERATOR = 1600, + MIOPEN_ATTR_REDUCTION_COMP_TYPE = 1601, + + MIOPEN_ATTR_OPERATION_REDUCTION_XDESC = 1610, + MIOPEN_ATTR_OPERATION_REDUCTION_YDESC = 1611, + MIOPEN_ATTR_OPERATION_REDUCTION_DESC = 1612, + + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_MATH_PREC = 1620, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_MEAN_DESC = 1621, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_INVSTD_DESC = 1622, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_BN_SCALE_DESC = 1623, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_X_DESC = 1624, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_DY_DESC = 1625, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_DBN_SCALE_DESC = 1626, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_DBN_BIAS_DESC = 1627, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_EQ_DY_SCALE_DESC = 1628, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_EQ_X_SCALE_DESC = 1629, + MIOPEN_ATTR_OPERATION_BN_BWD_WEIGHTS_EQ_BIAS = 1630, + + MIOPEN_ATTR_RESAMPLE_MODE = 1700, + MIOPEN_ATTR_RESAMPLE_COMP_TYPE = 1701, + MIOPEN_ATTR_RESAMPLE_SPATIAL_DIMS = 1702, + MIOPEN_ATTR_RESAMPLE_POST_PADDINGS = 1703, + MIOPEN_ATTR_RESAMPLE_PRE_PADDINGS = 1704, + MIOPEN_ATTR_RESAMPLE_STRIDES = 1705, + MIOPEN_ATTR_RESAMPLE_WINDOW_DIMS = 1706, + MIOPEN_ATTR_RESAMPLE_NAN_PROPAGATION = 1707, + MIOPEN_ATTR_RESAMPLE_PADDING_MODE = 1708, + + MIOPEN_ATTR_OPERATION_RESAMPLE_FWD_XDESC = 1710, + MIOPEN_ATTR_OPERATION_RESAMPLE_FWD_YDESC = 1711, + MIOPEN_ATTR_OPERATION_RESAMPLE_FWD_IDXDESC = 1712, + MIOPEN_ATTR_OPERATION_RESAMPLE_FWD_ALPHA = 1713, + MIOPEN_ATTR_OPERATION_RESAMPLE_FWD_BETA = 1714, + MIOPEN_ATTR_OPERATION_RESAMPLE_FWD_DESC = 1716, + + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_DXDESC = 1720, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_DYDESC = 1721, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_IDXDESC = 1722, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_ALPHA = 1723, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_BETA = 1724, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_DESC = 1725, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_XDESC = 1726, + MIOPEN_ATTR_OPERATION_RESAMPLE_BWD_YDESC = 1727, + + MIOPEN_ATTR_OPERATION_CONCAT_AXIS = 1800, + MIOPEN_ATTR_OPERATION_CONCAT_INPUT_DESCS = 1801, + MIOPEN_ATTR_OPERATION_CONCAT_INPLACE_INDEX = 1802, + MIOPEN_ATTR_OPERATION_CONCAT_OUTPUT_DESC = 1803, + + MIOPEN_ATTR_OPERATION_SIGNAL_MODE = 1900, + MIOPEN_ATTR_OPERATION_SIGNAL_FLAGDESC = 1901, + MIOPEN_ATTR_OPERATION_SIGNAL_VALUE = 1902, + MIOPEN_ATTR_OPERATION_SIGNAL_XDESC = 1903, + MIOPEN_ATTR_OPERATION_SIGNAL_YDESC = 1904, + + MIOPEN_ATTR_OPERATION_NORM_FWD_MODE = 2000, + MIOPEN_ATTR_OPERATION_NORM_FWD_PHASE = 2001, + MIOPEN_ATTR_OPERATION_NORM_FWD_XDESC = 2002, + MIOPEN_ATTR_OPERATION_NORM_FWD_MEAN_DESC = 2003, + MIOPEN_ATTR_OPERATION_NORM_FWD_INV_VARIANCE_DESC = 2004, + MIOPEN_ATTR_OPERATION_NORM_FWD_SCALE_DESC = 2005, + MIOPEN_ATTR_OPERATION_NORM_FWD_BIAS_DESC = 2006, + MIOPEN_ATTR_OPERATION_NORM_FWD_EPSILON_DESC = 2007, + MIOPEN_ATTR_OPERATION_NORM_FWD_EXP_AVG_FACTOR_DESC = 2008, + MIOPEN_ATTR_OPERATION_NORM_FWD_INPUT_RUNNING_MEAN_DESC = 2009, + MIOPEN_ATTR_OPERATION_NORM_FWD_INPUT_RUNNING_VAR_DESC = 2010, + MIOPEN_ATTR_OPERATION_NORM_FWD_OUTPUT_RUNNING_MEAN_DESC = 2011, + MIOPEN_ATTR_OPERATION_NORM_FWD_OUTPUT_RUNNING_VAR_DESC = 2012, + MIOPEN_ATTR_OPERATION_NORM_FWD_YDESC = 2013, + MIOPEN_ATTR_OPERATION_NORM_FWD_PEER_STAT_DESCS = 2014, + + MIOPEN_ATTR_OPERATION_NORM_BWD_MODE = 2100, + MIOPEN_ATTR_OPERATION_NORM_BWD_XDESC = 2101, + MIOPEN_ATTR_OPERATION_NORM_BWD_MEAN_DESC = 2102, + MIOPEN_ATTR_OPERATION_NORM_BWD_INV_VARIANCE_DESC = 2103, + MIOPEN_ATTR_OPERATION_NORM_BWD_DYDESC = 2104, + MIOPEN_ATTR_OPERATION_NORM_BWD_SCALE_DESC = 2105, + MIOPEN_ATTR_OPERATION_NORM_BWD_EPSILON_DESC = 2106, + MIOPEN_ATTR_OPERATION_NORM_BWD_DSCALE_DESC = 2107, + MIOPEN_ATTR_OPERATION_NORM_BWD_DBIAS_DESC = 2108, + MIOPEN_ATTR_OPERATION_NORM_BWD_DXDESC = 2109, + MIOPEN_ATTR_OPERATION_NORM_BWD_PEER_STAT_DESCS = 2110, + + MIOPEN_ATTR_OPERATION_RESHAPE_XDESC = 2200, + MIOPEN_ATTR_OPERATION_RESHAPE_YDESC = 2201, + + MIOPEN_ATTR_RNG_DISTRIBUTION = 2300, + MIOPEN_ATTR_RNG_NORMAL_DIST_MEAN = 2301, + MIOPEN_ATTR_RNG_NORMAL_DIST_STANDARD_DEVIATION = 2302, + MIOPEN_ATTR_RNG_UNIFORM_DIST_MAXIMUM = 2303, + MIOPEN_ATTR_RNG_UNIFORM_DIST_MINIMUM = 2304, + MIOPEN_ATTR_RNG_BERNOULLI_DIST_PROBABILITY = 2305, + + MIOPEN_ATTR_OPERATION_RNG_YDESC = 2310, + MIOPEN_ATTR_OPERATION_RNG_SEED = 2311, + MIOPEN_ATTR_OPERATION_RNG_DESC = 2312, + MIOPEN_ATTR_OPERATION_RNG_OFFSET_DESC = 2313, + +} miopenBackendAttributeName_t; + +/*! @brief Data type of an attribute of a backend descriptor + * + * Specifies the data type of an attribute of a backend descriptor. + * It is used to specify the type of data pointed to by the + * void *arrayOfElements argument of miopenBackendSetAttribute() + * and miopenBackendGetAttribute() + */ +typedef enum +{ + MIOPEN_TYPE_HANDLE = 0, /*!< miopenHandle_t */ + MIOPEN_TYPE_DATA_TYPE, /*!< miopenDataType_t */ + MIOPEN_TYPE_BOOLEAN, /*!< bool */ + MIOPEN_TYPE_INT64, /*!< int64_t */ + MIOPEN_TYPE_FLOAT, /*!< float */ + MIOPEN_TYPE_DOUBLE, /*!< double */ + MIOPEN_TYPE_VOID_PTR, /*!< void * */ + MIOPEN_TYPE_CONVOLUTION_MODE, /*!< miopenConvolutionMode_t */ + MIOPEN_TYPE_HEUR_MODE, /*!< miopenBackendHeurMode_t */ + MIOPEN_TYPE_KNOB_TYPE, /*!< miopenBackendKnobType_t */ + MIOPEN_TYPE_NAN_PROPOGATION, /*!< miopenNanPropagation_t */ + MIOPEN_TYPE_NUMERICAL_NOTE, /*!< miopenBackendNumericalNote_t */ + MIOPEN_TYPE_LAYOUT_TYPE, /*!< miopenBackendLayoutType_t */ + MIOPEN_TYPE_ATTRIB_NAME, /*!< miopenBackendAttributeName_t */ + MIOPEN_TYPE_POINTWISE_MODE, /*!< miopenPointwiseMode_t */ + MIOPEN_TYPE_BACKEND_DESCRIPTOR, /*!< miopenBackendDescriptor_t */ + MIOPEN_TYPE_GENSTATS_MODE, /*!< miopenGenStatsMode_t */ + MIOPEN_TYPE_BN_FINALIZE_STATS_MODE, /*!< miopenBnFinalizeStatsMode_t */ + MIOPEN_TYPE_REDUCTION_OPERATOR_TYPE, /*!< miopenReduceTensorOp_t */ + MIOPEN_TYPE_BEHAVIOR_NOTE, /*!< miopenBackendBehaviorNote_t */ + MIOPEN_TYPE_TENSOR_REORDERING_MODE, /*!< miopenBackendTensorReordering_t */ + MIOPEN_TYPE_RESAMPLE_MODE, /*!< miopenResampleMode_t */ + MIOPEN_TYPE_PADDING_MODE, /*!< miopenPaddingMode_t */ + MIOPEN_TYPE_INT32, /*!< int32_t */ + MIOPEN_TYPE_CHAR, /*!< char */ + MIOPEN_TYPE_SIGNAL_MODE, /*!< miopenSignalMode_t */ + MIOPEN_TYPE_FRACTION, /*!< miopenFraction_t */ + MIOPEN_TYPE_NORM_MODE, /*!< miopenBackendNormMode_t */ + MIOPEN_TYPE_NORM_FWD_PHASE, /*!< miopenBackendNormFwdPhase_t */ + MIOPEN_TYPE_RNG_DISTRIBUTION /*!< miopenRngDistribution_t */ +} miopenBackendAttributeType_t; + +/*! @brief Intended poinwise math operation for a pointwise operation descriptor + * + * An enumerated type to indicate the intended pointwise math operation in the backend pointwise + * operation descriptor + */ +typedef enum +{ + /*! A pointwise addition between two tensors is computed.*/ + MIOPEN_POINTWISE_ADD, + + /*! A pointwise addition between the first tensor and the square of the second tensor is + computed. */ + MIOPEN_POINTWISE_ADD_SQUARE, + + /*! A pointwise true division of the first tensor by second tensor is computed. */ + MIOPEN_POINTWISE_DIV, + + /*! A pointwise maximum is taken between two tensors. */ + MIOPEN_POINTWISE_MAX, + + /*! A pointwise minimum is taken between two tensors. */ + MIOPEN_POINTWISE_MIN, + + /*! A pointwise floating-point remainder of the first tensor’s division by the second tensor is + computed. */ + MIOPEN_POINTWISE_MOD, + + /*! A pointwise multiplication between two tensors is computed. */ + MIOPEN_POINTWISE_MUL, + + /*! A pointwise value from the first tensor to the power of the second tensor is computed. */ + MIOPEN_POINTWISE_POW, + + /*! A pointwise subtraction between two tensors is computed. */ + MIOPEN_POINTWISE_SUB, + + /*! A pointwise absolute value of the input tensor is computed. */ + MIOPEN_POINTWISE_ABS, + + /*! A pointwise ceiling of the input tensor is computed. */ + MIOPEN_POINTWISE_CEIL, + + /*! A pointwise trigonometric cosine of the input tensor is computed. */ + MIOPEN_POINTWISE_COS, + + /*! A pointwise exponential of the input tensor is computed. */ + MIOPEN_POINTWISE_EXP, + + /*! A pointwise floor of the input tensor is computed. */ + MIOPEN_POINTWISE_FLOOR, + + /*! A pointwise natural logarithm of the input tensor is computed. */ + MIOPEN_POINTWISE_LOG, + + /*! A pointwise numerical negative of the input tensor is computed. */ + MIOPEN_POINTWISE_NEG, + + /*! A pointwise reciprocal of the square root of the input tensor is computed. */ + MIOPEN_POINTWISE_RSQRT, + + /*! A pointwise trigonometric sine of the input tensor is computed. */ + MIOPEN_POINTWISE_SIN, + + /*! A pointwise square root of the input tensor is computed. */ + MIOPEN_POINTWISE_SQRT, + + /*! A pointwise trigonometric tangent of the input tensor is computed. */ + MIOPEN_POINTWISE_TAN, + + /*! A pointwise Error Function is computed. */ + MIOPEN_POINTWISE_ERF, + + /*! No computation is performed. As with other pointwise modes, this mode provides implicit + conversions by specifying the data type of the input tensor as one type, and the data type of + the output tensor as another. */ + MIOPEN_POINTWISE_IDENTITY, + + /*! A pointwise rectified linear activation function of the input tensor is computed. */ + MIOPEN_POINTWISE_RELU_FWD, + + /*! A pointwise tanh activation function of the input tensor is computed. */ + MIOPEN_POINTWISE_TANH_FWD, + + /*! A pointwise sigmoid activation function of the input tensor is computed. */ + MIOPEN_POINTWISE_SIGMOID_FWD, + + /*! A pointwise Exponential Linear Unit activation function of the input tensor is computed. */ + MIOPEN_POINTWISE_ELU_FWD, + + /*! A pointwise Gaussian Error Linear Unit activation function of the input tensor is computed. + */ + MIOPEN_POINTWISE_GELU_FWD, + + /*! A pointwise softplus activation function of the input tensor is computed. */ + MIOPEN_POINTWISE_SOFTPLUS_FWD, + + /*! A pointwise swish activation function of the input tensor is computed. */ + MIOPEN_POINTWISE_SWISH_FWD, + + /*! A pointwise tanh approximation of the Gaussian Error Linear Unit activation function of the + input tensor is computed. The tanh GELU approximation is computed as \f$0.5x\left( + 1+\tanh\left[ \sqrt{2/\pi}\left( x+0.044715x^{3} \right) \right] \right)\f$ */ + MIOPEN_POINTWISE_GELU_APPROX_TANH_FWD, + + /*! A pointwise first derivative of rectified linear activation of the input tensor is computed. + */ + MIOPEN_POINTWISE_RELU_BWD, + + /*! A pointwise first derivative of tanh activation of the input tensor is computed. */ + MIOPEN_POINTWISE_TANH_BWD, + + /*! A pointwise first derivative of sigmoid activation of the input tensor is computed. */ + MIOPEN_POINTWISE_SIGMOID_BWD, + + /*! A pointwise first derivative of Exponential Linear Unit activation of the input tensor is + computed. */ + MIOPEN_POINTWISE_ELU_BWD, + + /*! A pointwise first derivative of Gaussian Error Linear Unit activation of the input tensor is + computed. */ + MIOPEN_POINTWISE_GELU_BWD, + + /*! A pointwise first derivative of softplus activation of the input tensor is computed. */ + MIOPEN_POINTWISE_SOFTPLUS_BWD, + + /*! A pointwise first derivative of swish activation of the input tensor is computed. */ + MIOPEN_POINTWISE_SWISH_BWD, + + /*! A pointwise first derivative of the tanh approximation of the Gaussian Error Linear Unit + activation of the input tensor is computed. This is computed as \f$0.5\left( 1+\tanh\left( + b\left( x+cx^{3} \right) \right)+bxsech^{2}\left( b\left( cx^{3}+x \right) \right)\left( + 3cx^{2}+1 \right)dy \right)\f$ where \f$b\f$ is \f$\sqrt{2/\pi}\f$ and \f$c\f$ is + \f$0.044715\f$ */ + MIOPEN_POINTWISE_GELU_APPROX_TANH_BWD, + + /*! A pointwise truth value of the first tensor equal to the second tensor is computed. */ + MIOPEN_POINTWISE_CMP_EQ, + + /*! A pointwise truth value of the first tensor not equal to the second tensor is computed. */ + MIOPEN_POINTWISE_CMP_NEQ, + + /*! A pointwise truth value of the first tensor greater than the second tensor is computed. */ + MIOPEN_POINTWISE_CMP_GT, + + /*! A pointwise truth value of the first tensor greater than equal to the second tensor is + computed. */ + MIOPEN_POINTWISE_CMP_GE, + + /*! A pointwise truth value of the first tensor less than the second tensor is computed. */ + MIOPEN_POINTWISE_CMP_LT, + + /*! A pointwise truth value of the first tensor less than equal to the second tensor is + computed. */ + MIOPEN_POINTWISE_CMP_LE, + + /*! A pointwise truth value of the first tensor logical AND second tensor is computed. */ + MIOPEN_POINTWISE_LOGICAL_AND, + + /*! A pointwise truth value of the first tensor logical OR second tensor is computed. */ + MIOPEN_POINTWISE_LOGICAL_OR, + + /*! A pointwise truth value of input tensors logical NOT is computed. */ + MIOPEN_POINTWISE_LOGICAL_NOT, + + /*! A pointwise index value of the input tensor is generated along a given axis. */ + MIOPEN_POINTWISE_GEN_INDEX, + + /*! A pointwise value is selected amongst two input tensors based on a given predicate tensor. + */ + MIOPEN_POINTWISE_BINARY_SELECT, + + /*! A pointwise reciprocal of the input tensor is computed. In other words, for every element x + in the input tensor, 1/x is computed. */ + MIOPEN_POINTWISE_RECIPROCAL +} miopenPointwiseMode_t; + +/*! @brief Distribution for random number generation + * + * An enumerated type to indicate the distribution to be used in the backend Rng (random number + * generator) operation. + */ +typedef enum +{ + MIOPEN_RNG_DISTRIBUTION_BERNOULLI, + MIOPEN_RNG_DISTRIBUTION_UNIFORM, + MIOPEN_RNG_DISTRIBUTION_NORMAL, +} miopenRngDistribution_t; + +/*! @brief Backend descriptor + * + * A typedef void pointer to one of many opaque descriptor structures. + * The type of structure that it points to is determined by the argument when allocating the memory + * for the opaque structure using miopenBackendCreateDescriptor(). + * + * Attributes of a descriptor can be set using miopenBackendSetAttribute(). After all required + * attributes of a descriptor are set, the descriptor can be finalized by miopenBackendFinalize(). + * From a finalized descriptor, one can query its queryable attributes using + * miopenBackendGetAttribute(). Finally, the memory allocated for a descriptor can be freed using + * miopenBackendDestroyDescriptor(). + */ +MIOPEN_DECLARE_OBJECT(miopenBackendDescriptor) + +/*! @brief Creates a backend descriptor + * + * Allocates memory for a given descriptorType at the location pointed + * by the descriptor + * + * @param [in] descriptorType One among the enumerated miopenBackendDescriptorType_t + * @param [out] descriptor Pointer to a descriptor + * + * @retval miopenStatusSuccess The creation was successful + * @retval miopenStatusUnsupportedOp Creating a descriptor of a given type is not supported + * @retval miopenStatusAllocFailed The memory allocation failed + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendCreateDescriptor( + miopenBackendDescriptorType_t descriptorType, miopenBackendDescriptor_t* descriptor); + +/*! @brief Sets an attribute of a descriptor + * + * This function sets an attribute of a descriptor to values provided as a pointer. + * Returns miopenStatusUnsupportedOp if the descriptor is already + * successfully finalized using miopenBackendFinalize(). + * + * @param [in] descriptor Instance of miopenBackendDescriptor_t whose attribute is being set + * @param [in] attributeName The name of the attribute being set on the descriptor + * @param [in] attributeType The type of attribute + * @param [in] elementCount Number of elements being set + * @param [in] arrayOfElements The starting location for an array from where to read the values + * from. The elements of the array are expected to be of the datatype + * of the attributeType. The datatype of the attributeType is listed + * in the mapping table of miopenBackendAttributeType_t. + * + * @retval miopenStatusSuccess The attributeName was set to the descriptor + * @retval miopenStatusNotInitialized The backend descriptor pointed to by the descriptor is + * already in the finalized state + * @retval miopenStatusBadParm The function is called with arguments that correspond to + * invalid values. Some examples include: + * * attributeName is not a settable attribute of descriptor. + * * attributeType is incorrect for this attributeName. + * * elemCount value is unexpected. + * * arrayOfElements contains values invalid for the + * attributeType. + * @retval miopenStatusUnsupportedOp The values to which the attributes are being set are not + * supported by the current version + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendSetAttribute(miopenBackendDescriptor_t descriptor, + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + +/*! @brief Finalizes a backend descriptor + * + * Finalizes the memory pointed to by the descriptor. The type of finalization is done depending on + * the descriptorType argument with which the descriptor was created using + * miopenBackendCreateDescriptor() or initialized using miopenBackendInitialize(). + * + * @param [in] descriptor Instance of miopenBackendDescriptor_t to finalize + * + * @retval miopenStatusSuccess The descriptor was finalized successfully + * @retval miopenStatusBadParm Invalid descriptor attribute values or combination thereof is + * encountered + * @retval miopenStatusUnsupportedOp Descriptor attribute values or combinations therefore not + * supported by the current version + * @retval miopenStatusInternalError Some internal errors are encountered + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendFinalize(miopenBackendDescriptor_t descriptor); + +/*! @brief Retrieves backend descriptor's attribute + * + * This function retrieves the values of an attribute of a descriptor. attributeName is the name of + * the attribute whose value is requested. attributeType is the type of attribute. + * requestsedElementCount is the number of elements to be potentially retrieved. The number of + * elements for the requested attribute is stored in elementCount. The retrieved values are stored + * in arrayOfElements. When the attribute is expected to have a single value, arrayOfElements can be + * pointer to the output value. This function will return miopenStatusNotInitialized if the + * descriptor has not been successfully finalized using miopenBackendFinalize() + * + * @param [in] descriptor Instance of miopenBackendDescriptor_t whose attribute to + * retrieve + * @param [in] attributeName The name of the attribute being get from the descriptor + * @param [in] attributeType The type of attribute + * @param [in] requestedElementCount Number of elements to output to arrayOfElements + * @param [out] elementCount Output pointer for the number of elements the descriptor + * attribute has. Note that miopenBackendGetAttribute() will + * only write the least of this and requestedElementCount + * elements to arrayOfElements + * @param [out] arrayOfElements Array of elements of the datatype of the attributeType. The + * data type of the attributeType is listed in the mapping + * table of miopenBackendAttributeType_t + * + * @retval miopenStatusSuccess The attributeName was retrieved from the descriptor + * successfully + * @retval miopenStatusBadParm One or more invalid or inconsistent argument values were + * encountered. Some examples include: + * * attributeName is not a valid attribute for the descriptor. + * * attributeType is not one of the valid types for the + * attribute. + * @retval miopenStatusNotInitialized The descriptor has not been successfully finalized using + * miopenBackendFinalize() + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendGetAttribute(miopenBackendDescriptor_t descriptor, + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + +/*! @brief Executes a graph + * + * Executes the given Engine Configuration Plan on the VariantPack and the finalized ExecutionPlan + * on the data. The data and the working space are encapsulated in the VariantPack + * + * @param [in] handle An instance of miopenHandle_t + * @param [in] executionPlan Descriptor of the finalized ExecutionPlan + * @param [in] variantPack Descriptor of the finalized VariantPack consisting of: + * * Data pointer for each non-virtual pointer of the operation set in + * the execution plan. + * * Pointer to user-allocated workspace in global memory at least as + * large as the size queried + * + * @retval miopenStatusSuccess The ExecutionPlan was executed successfully + * @retval miopenStatusBadParm An incorrect or inconsistent value is encountered. For + * example, a required data pointer is invalid + * @retval miopenStatusInternalError Some internal errors were encountered + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendExecute(miopenHandle_t handle, + miopenBackendDescriptor_t executionPlan, + miopenBackendDescriptor_t variantPack); + +/*! @brief Destroys an instance of miopenBackendDescriptor_t + * + * Destroys instances of miopenBackendDescriptor_t that were previously created using + * miopenBackendCreateDescriptor(). The value pointed by the descriptor will be undefined after the + * memory is free and done. + * + * **Undefined Behavior** if the descriptor was altered between the 'Create' and 'Destroy + * Descriptor' + * + * @param [in] descriptor Instance of miopenBackendDescriptor_t previously created by + * miopenBackendCreateDescriptor() + * + * @retval miopenStatusSuccess The memory was destroyed successfully + * @retval miopenStatusAllocFailed The destruction of memory failed + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendDestroyDescriptor(miopenBackendDescriptor_t descriptor); + +/*! @brief Repurposes an instance of miopenBackendDescriptor_t + * + * Repurposes a pre-allocated memory pointed to by a descriptor of size sizeInByte to a backend + * descriptor of type descriptorType. The finalized state of the descriptor is set to false. + * + * @param [in] descriptor Instance of miopenBackendDescriptor_t to be initialized + * @param [in] descriptorType Enumerated value for the type miopenBackendDescriptorType_t + * @param [in] sizeInBytes Size of memory pointed to by descriptor + * + * @retval miopenStatusSuccess The memory was initialized successfully + * @retval miopenStatusBadParm An invalid or inconsistent argument value is encountered. Some + * examples include: + * * descriptor is a nullptr + * * sizeInBytes is less than the size required by the descriptor + * type + * @retval miopenStatusUnknownError The error information was not gathered + */ +MIOPEN_EXPORT miopenStatus_t miopenBackendInitialize(miopenBackendDescriptor_t descriptor, + miopenBackendDescriptorType_t descriptorType, + size_t sizeInBytes); + +/** @} */ +// CLOSEOUT BackendAPI DOXYGEN GROUP +#endif // MIOPEN_BETA_API + #ifdef __cplusplus } #endif diff --git a/install_deps.cmake b/install_deps.cmake index 0d2f76348d..8e3a71d906 100755 --- a/install_deps.cmake +++ b/install_deps.cmake @@ -113,5 +113,5 @@ cget(init ${TOOLCHAIN_FLAG} -DCMAKE_INSTALL_RPATH=${PREFIX}/lib ${PARSE_UNPARSED cget(ignore pcre) # Install dependencies -cget(install -U ROCmSoftwarePlatform/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8) +cget(install -U ROCm/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8) cget(install -U -f requirements.txt) diff --git a/rbuild.ini b/rbuild.ini index 1344c70b49..2ab56e33f2 100755 --- a/rbuild.ini +++ b/rbuild.ini @@ -3,7 +3,7 @@ cxx = ${rocm_path}/llvm/bin/clang++ cc = ${rocm_path}/llvm/bin/clang ignore = pcre deps = - ROCmSoftwarePlatform/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8 + ROCm/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8 -f requirements.txt [develop] @@ -20,5 +20,5 @@ cxx = ${rocm_path}/llvm/bin/clang++ cc = ${rocm_path}/llvm/bin/clang ignore = pcre deps = - ROCmSoftwarePlatform/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8 + ROCm/rocm-recipes@329203d79f9fe77ae5d0d742af0966bc57f4dfc8 -f dev-requirements.txt diff --git a/requirements.txt b/requirements.txt index 6afa53b6c1..b2a5f64172 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ sqlite3@3.43.2 -DCMAKE_POSITION_INDEPENDENT_CODE=On boost@1.83 -DCMAKE_POSITION_INDEPENDENT_CODE=On --build -DCMAKE_CXX_FLAGS=" -std=c++14 -Wno-enum-constexpr-conversion -Wno-deprecated-builtins -Wno-deprecated-declarations " facebook/zstd@v1.4.5 -X subdir -DCMAKE_DIR=build/cmake -# ROCmSoftwarePlatform/half@10abd99e7815f0ca5d892f58dd7d15a23b7cf92c --build -ROCmSoftwarePlatform/rocMLIR@rocm-5.5.0 -H sha256:a5f62769d28a73e60bc8d61022820f050e97c977c8f6f6275488db31512e1f42 -DBUILD_FAT_LIBROCKCOMPILER=1 -DCMAKE_IGNORE_PATH="/opt/conda/envs/py_3.8;/opt/conda/envs/py_3.9;/opt/conda/envs/py_3.10" -DCMAKE_IGNORE_PREFIX_PATH=/opt/conda +# ROCm/half@10abd99e7815f0ca5d892f58dd7d15a23b7cf92c --build +ROCm/rocMLIR@rocm-5.5.0 -H sha256:a5f62769d28a73e60bc8d61022820f050e97c977c8f6f6275488db31512e1f42 -DBUILD_FAT_LIBROCKCOMPILER=1 -DCMAKE_IGNORE_PATH="/opt/conda/envs/py_3.8;/opt/conda/envs/py_3.9;/opt/conda/envs/py_3.10" -DCMAKE_IGNORE_PREFIX_PATH=/opt/conda nlohmann/json@v3.11.2 -DJSON_MultipleHeaders=ON -DJSON_BuildTests=Off -ROCmSoftwarePlatform/FunctionalPlus@v0.2.18-p0 -ROCmSoftwarePlatform/eigen@3.4.0 -ROCmSoftwarePlatform/frugally-deep@9683d557eb672ee2304f80f6682c51242d748a50 -ROCmSoftwarePlatform/composable_kernel@b30d416c973fdb98c9496d0bf4501cd0494f413c -DCMAKE_BUILD_TYPE=Release -DINSTANCES_ONLY=ON +ROCm/FunctionalPlus@v0.2.18-p0 +ROCm/eigen@3.4.0 +ROCm/frugally-deep@9683d557eb672ee2304f80f6682c51242d748a50 +ROCm/composable_kernel@f0759faff4a1c3ba5f739dfed468530e0ee9f28b -DCMAKE_BUILD_TYPE=Release -DINSTANCES_ONLY=ON diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3a39cf996d..2c38414952 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -121,6 +121,15 @@ set( MIOpen_Source fusion.cpp fusion/problem_description.cpp generic_search.cpp + graphapi/convolution.cpp + graphapi/graphapi.cpp + graphapi/opgraph.cpp + graphapi/matmul.cpp + graphapi/pointwise.cpp + graphapi/reduction.cpp + graphapi/rng.cpp + graphapi/tensor.cpp + graphapi/variant_pack.cpp groupnorm_api.cpp groupnorm/problem_description.cpp handle_api.cpp @@ -133,6 +142,8 @@ set( MIOpen_Source lock_file.cpp logger.cpp lrn_api.cpp + mha/mha_descriptor.cpp + mha/problem_description.cpp op_args.cpp operator.cpp performance_config.cpp @@ -249,11 +260,14 @@ set( MIOpen_Source solver/fft.cpp solver/gemm.cpp solver/gemm_bwd.cpp + solver/gemm_common.cpp solver/gemm_wrw.cpp solver/groupnorm/forward_groupnorm.cpp solver/layernorm/forward_layernorm.cpp solver/layernorm/forward_layernorm2d_ck.cpp solver/layernorm/forward_layernorm4d_ck.cpp + solver/mha/mha_solver_backward.cpp + solver/mha/mha_solver_forward.cpp solver/pooling/forward2d.cpp solver/pooling/forwardNaive.cpp solver/pooling/forwardNd.cpp @@ -405,6 +419,7 @@ if( MIOPEN_BACKEND MATCHES "OpenCL" OR MIOPEN_BACKEND STREQUAL "HIPOC" OR MIOPEN kernels/inst_wrappers.inc kernels/miopen_cstdint.hpp kernels/miopen_limits.hpp + kernels/miopen_rocrand.hpp kernels/miopen_type_traits.hpp kernels/miopen_utility.hpp kernels/neuron.inc @@ -768,7 +783,7 @@ if(MIOPEN_ENABLE_AI_KERNEL_TUNING OR MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK) endif() foreach(MODEL_FILE ${MODEL_FILES}) get_filename_component(MODEL_FILE_FILENAME "${MODEL_FILE}" NAME) - configure_file("${MODEL_FILE}" "${PROJECT_BINARY_DIR}/${DATABASE_INSTALL_DIR}/${MODEL_FILE_FILENAME}" COPYONLY) + configure_file("${MODEL_FILE}" "${PROJECT_BINARY_DIR}/${DATABASE_INSTALL_DIR}/${MODEL_FILE_FILENAME}" COPYONLY) endforeach() endif() @@ -783,7 +798,7 @@ elseif(MIOPEN_BACKEND STREQUAL "HIPOC" OR MIOPEN_BACKEND STREQUAL "HIP") target_link_libraries( MIOpen PRIVATE hip::device ) target_link_libraries( MIOpen INTERFACE hip::host ) if(MIOPEN_USE_HIPRTC) - if(WIN32) + if(WIN32) target_link_libraries( MIOpen PRIVATE hiprtc::hiprtc ) else() target_link_libraries( MIOpen PRIVATE hiprtc) @@ -803,6 +818,11 @@ if(MIOPEN_USE_COMGR) target_internal_library(MIOpen amd_comgr) endif() +if(MIOPEN_OFFLINE_COMPILER_PATHS_V2) + # Adding rocm-core library dependency for API getROCmInstallPath() + target_link_libraries(MIOpen PRIVATE rocm-core) +endif() + if(rocblas_FOUND) target_link_libraries( MIOpen INTERFACE $ ) target_link_libraries( MIOpen PRIVATE roc::rocblas ) @@ -907,7 +927,7 @@ if(NOT MIOPEN_EMBED_DB STREQUAL "") message(STATUS "Adding find db for arch: ${EMBED_ARCH}") list(APPEND CODE_OBJECTS "${PROJECT_BINARY_DIR}/${DATABASE_INSTALL_DIR}/${EMBED_ARCH}.${MIOPEN_BACKEND}.fdb.txt") message(STATUS "Adding perf db for arch: ${EMBED_ARCH}") - list(APPEND CODE_OBJECTS "${PROJECT_BINARY_DIR}/${DATABASE_INSTALL_DIR}/${EMBED_ARCH}.db") + list(APPEND CODE_OBJECTS "${PROJECT_BINARY_DIR}/${DATABASE_INSTALL_DIR}/${EMBED_ARCH}.db${PERFDB_SUFFIX}") endforeach() # Embed Bin Cache if(NOT MIOPEN_BINCACHE_PATH STREQUAL "") diff --git a/src/api/find2_0_commons.cpp b/src/api/find2_0_commons.cpp index ce203197a5..5f79ba59cc 100644 --- a/src/api/find2_0_commons.cpp +++ b/src/api/find2_0_commons.cpp @@ -89,6 +89,22 @@ miopenStatus_t miopenCreateBiasProblem(miopenProblem_t* problem, miopenProblemDi }); } +miopenStatus_t miopenCreateMhaProblem(miopenProblem_t* problem, + miopenMhaDescriptor_t operatorDesc, + miopenProblemDirection_t direction) +{ + MIOPEN_LOG_FUNCTION(problem, direction); + return MakeProblem(problem, operatorDesc, direction); +} + +miopenStatus_t miopenCreateSoftmaxProblem(miopenProblem_t* problem, + miopenSoftmaxDescriptor_t operatorDesc, + miopenProblemDirection_t direction) +{ + MIOPEN_LOG_FUNCTION(problem, direction); + return MakeProblem(problem, operatorDesc, direction); +} + miopenStatus_t miopenFuseProblems(miopenProblem_t problem1, miopenProblem_t problem2) { MIOPEN_LOG_FUNCTION(problem1, problem2); @@ -263,6 +279,43 @@ inline std::ostream& operator<<(std::ostream& stream, const miopenTensorArgument case miopenTensorBias: stream << "Bias"; break; case miopenTensorBiasX: stream << "BiasX"; break; case miopenTensorBiasY: stream << "BiasY"; break; + case miopenTensorMhaK: stream << "MhaK"; break; + case miopenTensorMhaQ: stream << "MhaQ"; break; + case miopenTensorMhaV: stream << "MhaV"; break; + case miopenTensorMhaDescaleK: stream << "MhaDescaleK"; break; + case miopenTensorMhaDescaleQ: stream << "DescaleQ"; break; + case miopenTensorMhaDescaleV: stream << "DescaleV"; break; + case miopenTensorMhaDescaleS: stream << "MhaDescaleS"; break; + case miopenTensorMhaScaleS: stream << "MhaScaleS"; break; + case miopenTensorMhaScaleO: stream << "MhaScaleO"; break; + case miopenTensorMhaDropoutProbability: stream << "MhaDropoutProbability"; break; + case miopenTensorMhaDropoutSeed: stream << "MhaDropoutSeed"; break; + case miopenTensorMhaDropoutOffset: stream << "MhaDropoutOffset"; break; + case miopenTensorMhaO: stream << "MhaO"; break; + case miopenTensorMhaAmaxO: stream << "MhaAmaxO"; break; + case miopenTensorMhaAmaxS: stream << "MhaAmaxS"; break; + case miopenTensorMhaM: stream << "MhaM"; break; + case miopenTensorMhaZInv: stream << "MhaZInv"; break; + case miopenTensorMhaDO: stream << "miopenTensorMhaDO"; break; + case miopenTensorMhaDescaleO: stream << "miopenTensorMhaDescaleO"; break; + case miopenTensorMhaDescaleDO: stream << "miopenTensorMhaDescaleDO"; break; + case miopenTensorMhaDescaleDS: stream << "miopenTensorMhaDescaleDS"; break; + case miopenTensorMhaScaleDS: stream << "miopenTensorMhaScaleDS"; break; + case miopenTensorMhaScaleDQ: stream << "miopenTensorMhaScaleDQ"; break; + case miopenTensorMhaScaleDK: stream << "miopenTensorMhaScaleDK"; break; + case miopenTensorMhaScaleDV: stream << "miopenTensorMhaScaleDV"; break; + case miopenTensorMhaDQ: stream << "miopenTensorMhaDQ"; break; + case miopenTensorMhaDK: stream << "miopenTensorMhaDK"; break; + case miopenTensorMhaDV: stream << "miopenTensorMhaDV"; break; + case miopenTensorMhaAmaxDQ: stream << "miopenTensorMhaAmaxDQ"; break; + case miopenTensorMhaAmaxDK: stream << "miopenTensorMhaAmaxDK"; break; + case miopenTensorMhaAmaxDV: stream << "miopenTensorMhaAmaxDV"; break; + case miopenTensorMhaAmaxDS: stream << "miopenTensorMhaAmaxDS"; break; + case miopenTensorSoftmaxX: stream << "SoftmaxX"; break; + case miopenTensorSoftmaxY: stream << "SoftmaxY"; break; + case miopenTensorSoftmaxDX: stream << "SoftmaxDX"; break; + case miopenTensorSoftmaxDY: stream << "SoftmaxDY"; break; + case miopenTensorArgumentIsScalar: stream << "ScalarArgument"; break; case miopenTensorArgumentIdInvalid: stream << "Invalid"; break; } diff --git a/src/binary_cache.cpp b/src/binary_cache.cpp index c94b0bcbab..7feb3b079b 100644 --- a/src/binary_cache.cpp +++ b/src/binary_cache.cpp @@ -140,36 +140,36 @@ KDb GetDb(const TargetProperties& target, size_t num_cu) if(!fs::exists(sys_path)) sys_path = fs::path{}; #endif - return {sys_path.string(), user_path.string()}; + return {DbKinds::KernelDb, sys_path.string(), user_path.string()}; } #endif fs::path GetCacheFile(const std::string& device, const std::string& name, const std::string& args) { - const std::string filename = name + ".o"; + const auto filename = make_object_file_name(name); return GetCachePath(false) / miopen::md5(device + ":" + args) / filename; } #if MIOPEN_ENABLE_SQLITE_KERN_CACHE -std::string LoadBinary(const TargetProperties& target, - const size_t num_cu, - const std::string& name, - const std::string& args) +std::vector LoadBinary(const TargetProperties& target, + const size_t num_cu, + const std::string& name, + const std::string& args) { if(miopen::IsCacheDisabled()) return {}; auto db = GetDb(target, num_cu); - const std::string filename = name + ".o"; - const KernelConfig cfg{filename, args, ""}; + const auto filename = make_object_file_name(name).string(); + const KernelConfig cfg{filename, args, {}}; MIOPEN_LOG_I2("Loading binary for: " << filename << "; args: " << args); auto record = db.FindRecord(cfg); if(record) { MIOPEN_LOG_I2("Successfully loaded binary for: " << filename << "; args: " << args); - return record.get(); + return *record; } else { @@ -178,7 +178,7 @@ std::string LoadBinary(const TargetProperties& target, } } -void SaveBinary(const std::string& hsaco, +void SaveBinary(const std::vector& hsaco, const TargetProperties& target, const std::size_t num_cu, const std::string& name, @@ -189,7 +189,7 @@ void SaveBinary(const std::string& hsaco, auto db = GetDb(target, num_cu); - const std::string filename = name + ".o"; + const auto filename = make_object_file_name(name).string(); KernelConfig cfg{filename, args, hsaco}; MIOPEN_LOG_I2("Saving binary for: " << filename << "; args: " << args); @@ -208,7 +208,7 @@ fs::path LoadBinary(const TargetProperties& target, auto f = GetCacheFile(target.DbId(), name, args); if(fs::exists(f)) { - return f.string(); + return f; } else { diff --git a/src/bz2.cpp b/src/bz2.cpp index a17b3f4e28..71c3161b93 100644 --- a/src/bz2.cpp +++ b/src/bz2.cpp @@ -25,6 +25,10 @@ *******************************************************************************/ #include +#include +#include +#include +#include namespace miopen { void check_bz2_error(int e, const std::string& name) @@ -55,15 +59,18 @@ void check_bz2_error(int e, const std::string& name) throw std::runtime_error(name + " failed: unknown error!"); } -std::string compress(std::string s, bool* compressed) +std::vector compress(const std::vector& v, bool* compressed) { - std::string result = s; - unsigned int len = result.size(); - auto e = BZ2_bzBuffToBuffCompress(&result[0], &len, &s[0], s.size(), 9, 0, 30); + auto result = v; + unsigned int len = result.size(); + // NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast) + auto e = BZ2_bzBuffToBuffCompress( + result.data(), &len, const_cast(v.data()), v.size(), 9, 0, 30); + // NOLINTEND(cppcoreguidelines-pro-type-const-cast) if(compressed != nullptr and e == BZ_OUTBUFF_FULL) { *compressed = false; - return s; + return v; } check_bz2_error(e, "BZ2_bzBuffToBuffCompress"); result.resize(len); @@ -72,11 +79,14 @@ std::string compress(std::string s, bool* compressed) return result; } -std::string decompress(std::string s, unsigned int size) +std::vector decompress(const std::vector& v, unsigned int size) { - std::string result(size, 0); + std::vector result(size, 0); unsigned int len = result.size(); - auto e = BZ2_bzBuffToBuffDecompress(&result[0], &len, &s[0], s.size(), 0, 0); + // NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast) + auto e = BZ2_bzBuffToBuffDecompress( + result.data(), &len, const_cast(v.data()), v.size(), 0, 0); + // NOLINTEND(cppcoreguidelines-pro-type-const-cast) check_bz2_error(e, "BZ2_bzBuffToBuffDecompress"); result.resize(len); return result; diff --git a/src/comgr.cpp b/src/comgr.cpp index 41af1d0e95..4a9c985273 100644 --- a/src/comgr.cpp +++ b/src/comgr.cpp @@ -37,11 +37,7 @@ #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #include #if MIOPEN_USE_HIPRTC #include @@ -60,6 +56,9 @@ /// More info at https://github.com/ROCm/MIOpen/issues/1257. #define WORKAROUND_ISSUE_1257 (HIP_PACKAGE_VERSION_FLAT >= 4003021331ULL) +/// https://github.com/ROCm/ROCm-CompilerSupport/issues/67 about unused -nogpulib. +#define WORKAROUND_ROCMCOMPILERSUPPORT_ISSUE_67 1 + MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_COMGR_LOG_CALLS) MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_COMGR_LOG_SOURCE_NAMES) @@ -970,9 +969,10 @@ void BuildAsm(const std::string& name, SetIsaName(action, target); action.SetLogging(true); auto optAsm = miopen::SplitSpaceSeparated(options); - if(target.Xnack() && !*target.Xnack()) - optAsm.emplace_back("-mno-xnack"); compiler::lc::gcnasm::RemoveOptionsUnwanted(optAsm); +#if WORKAROUND_ROCMCOMPILERSUPPORT_ISSUE_67 + optAsm.push_back("--rocm-path=."); +#endif action.SetOptionList(optAsm); const Dataset relocatable; diff --git a/src/conv/heuristics/ai_heuristics.cpp b/src/conv/heuristics/ai_heuristics.cpp index 3b7647f562..8b2f5e79b7 100644 --- a/src/conv/heuristics/ai_heuristics.cpp +++ b/src/conv/heuristics/ai_heuristics.cpp @@ -76,7 +76,11 @@ Metadata::Metadata(const std::string& arch) features_mean(common::LookupValues( features, json["stats"]["overall"]["features"]["mean"])), features_std(common::LookupValues( - features, json["stats"]["overall"]["features"]["std"])) + features, json["stats"]["overall"]["features"]["std"])), + test_features_mean(common::LookupValues( + features, json["stats"]["test"]["features"]["mean"])), + test_features_std(common::LookupValues( + features, json["stats"]["test"]["features"]["std"])) { } @@ -348,12 +352,107 @@ class Gfx90aModel final : public Model } }; +class Gfx942Model final : public Model +{ +public: + Gfx942Model() : Model("gfx942") {} + bool IsProblemSupported(const conv::ProblemDescription& problem, + const ExecutionContext& ctx) const override + { + // check if problem is of the kind TunaNet was trained to handle + if(!problem.Is2d()) + { + MIOPEN_LOG_I2("TunaNet Inapplicable: Problem not 2D"); + return false; + } + if(problem.GetInLayout() != "NCHW") + { + MIOPEN_LOG_I2("TunaNet Inapplicable: Layout not supported"); + return false; + } + if(problem.GetKernelStrideH() != problem.GetKernelStrideW()) + { + MIOPEN_LOG_I2("TunaNet Inapplicable: Stride must be equal along all axes"); + return false; + } + if(problem.GetDilationH() != problem.GetDilationW()) + { + MIOPEN_LOG_I2("TunaNet Inapplicable: Dilation must be 1"); + return false; + } + if(problem.GetBias() != 0) + { + MIOPEN_LOG_I2("TunaNet Inapplicable: Bias must be 0"); + return false; + } + const auto data_type = problem.GetInDataType(); + if(data_type != miopenFloat && data_type != miopenHalf && data_type != miopenBFloat16) + { + MIOPEN_LOG_I2("TunaNet Inapplicable: Unsupported data type"); + return false; + } + + // check if the context is s.t. no solver TunaNet may predict would be applicable + size_t applicable_solvers = 0; + for(const auto& solver_name : metadata.solver_map) + { + auto solver_id = solver::Id{solver_name.second}; + auto solver = solver_id.GetSolver(); + if(solver.IsApplicable(ctx, problem)) + { + applicable_solvers++; + break; + } + } + if(applicable_solvers == 0) + { + MIOPEN_LOG_I2("TunaNet Inapplicable: No solver that TunaNet may predict applies"); + return false; + } + MIOPEN_LOG_I2("TunaNet Applicable"); + return true; + } + +protected: + std::vector ToFeatures(const conv::ProblemDescription& problem) const override + { + const bool isFwd = problem.GetDirection() == conv::Direction::Forward; + std::vector features = { + static_cast(isFwd ? problem.GetInChannels() : problem.GetOutChannels()), + static_cast(isFwd ? problem.GetInHeight() : problem.GetOutHeight()), + static_cast(isFwd ? problem.GetInWidth() : problem.GetOutWidth()), + static_cast(isFwd ? problem.GetOutChannels() : problem.GetInChannels()), + static_cast(isFwd ? problem.GetOutHeight() : problem.GetInHeight()), + static_cast(isFwd ? problem.GetOutWidth() : problem.GetInWidth()), + static_cast(problem.GetWeightsHeight()), + static_cast(problem.GetWeightsWidth()), + static_cast(problem.GetPadH()), + static_cast(problem.GetPadW()), + static_cast(problem.GetKernelStrideH()), + static_cast(problem.GetKernelStrideW()), + static_cast(problem.GetDilationH()), + static_cast(problem.GetDilationW()), + static_cast(problem.GetOutBatchSize()), + static_cast(metadata.EncodePrecision(problem.GetInDataType())), + static_cast(metadata.EncodeDirection(problem.GetDirection())), + static_cast(problem.GetGroupCount())}; + + // normalize + for(size_t i = 0; i < features.size(); ++i) + features[i] = + (features[i] - metadata.test_features_mean[i]) / metadata.test_features_std[i]; + + return features; + } +}; + std::unique_ptr GetModel(const std::string& device) { + if(device == "gfx942") + return std::make_unique(); if(device == "gfx90a") return std::make_unique(); - else - return std::make_unique(); + return std::make_unique(); } std::vector PredictSolver(const conv::ProblemDescription& problem, @@ -435,7 +534,8 @@ Metadata::Metadata(const std::string& arch, const std::string& solver) { const nlohmann::json metadata = common::LoadJSON(GetSystemDbPath() + "/" + arch + "_" + solver + "_metadata.ktn.model"); - num_tuning_params = metadata["num_tuning_params"].get(); + num_tuning_params = + metadata["num_tuning_params"].get>(); tuning_decodings = metadata["decodings"]["tunings"].get>(); } @@ -507,6 +607,7 @@ std::shared_ptr GetModel(const std::string& arch, const std::string& solv bool ModelSetParams(const std::string& arch, const std::string& solver, + miopen::conv::Direction direction, const std::vector& features, bool transform_features, std::function validator) @@ -517,9 +618,19 @@ bool ModelSetParams(const std::string& arch, dim = std::sqrt(features.size()); else dim = features.size(); + auto start = std::chrono::high_resolution_clock::now(); fdeep::tensors context = model->Encode(features, dim, transform_features); float decoder_input = 0.0; - for(std::size_t i = 0; i < model->metadata.num_tuning_params; ++i) + std::string dir; + switch(direction) + { + case miopen::conv::Direction::Forward: dir = "fwd"; break; + case miopen::conv::Direction::BackwardData: dir = "bwd"; break; + case miopen::conv::Direction::BackwardWeights: dir = "wrw"; break; + default: return false; + } + std::size_t num_tuning_params = model->metadata.num_tuning_params[dir]; + for(std::size_t i = 0; i < num_tuning_params; ++i) { fdeep::tensors decoder_output = model->Decode(decoder_input, context); @@ -536,7 +647,12 @@ bool ModelSetParams(const std::string& arch, std::string value = model->metadata.tuning_decodings[std::to_string(token)]; pq.pop(); if(value == "-1") + { + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + MIOPEN_LOG_I2("Model ran for " << duration.count() << " micro-seconds"); return false; + } if(validator(i, value)) { output_token_index = @@ -547,6 +663,9 @@ bool ModelSetParams(const std::string& arch, decoder_input = float(output_token_index); context = {decoder_output.begin() + 1, decoder_output.end()}; } + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + MIOPEN_LOG_I2("Model ran for " << duration.count() << " micro-seconds"); return true; } diff --git a/src/conv/invokers/impl_gemm.cpp b/src/conv/invokers/impl_gemm.cpp index 2f07422a30..4ab0829c9d 100644 --- a/src/conv/invokers/impl_gemm.cpp +++ b/src/conv/invokers/impl_gemm.cpp @@ -139,8 +139,9 @@ InvokerFactory MakeImplGemmDataInvokerFactory(const ProblemDescription& problem) { if(tensors.wDesc.GetLengths()[2] == 1 && tensors.wDesc.GetLengths()[3] == 1) { // filter = 1 - if(tensors.wDesc.GetSize() == 4 || - (tensors.wDesc.GetSize() == 5 && tensors.wDesc.GetLengths()[4] == 1)) + if(tensors.wDesc.GetNumDims() == 4 || + (tensors.wDesc.GetNumDims() == 5 && + tensors.wDesc.GetLengths()[4] == 1)) { is_1x1_s1 = true; } diff --git a/src/convolution.cpp b/src/convolution.cpp index b19885a10e..1fd9a9054e 100644 --- a/src/convolution.cpp +++ b/src/convolution.cpp @@ -25,6 +25,7 @@ *******************************************************************************/ #include +#include #include #include #include @@ -354,7 +355,7 @@ ConvolutionDescriptor::GetForwardOutputTensorWithLayout(const TensorDescriptor& out_lens[0] = in_n; out_lens[1] = out_c; - const std::string default_layout = tensor_layout_get_default(xDesc.GetSize()); + const std::string default_layout = tensor_layout_get_default(xDesc.GetNumDims()); std::vector out_strides; tensor_layout_to_strides( out_lens, default_layout, yLayout, xDesc.GetVectorLength(), out_strides); @@ -372,7 +373,7 @@ TensorDescriptor ConvolutionDescriptor::GetForwardOutputTensor(const TensorDescr miopenDataType_t yType) const { // output layout same as input - const std::string default_layout = tensor_layout_get_default(xDesc.GetSize()); + const std::string default_layout = tensor_layout_get_default(xDesc.GetNumDims()); const std::string in_layout = xDesc.GetLayout(default_layout); return GetForwardOutputTensorWithLayout(xDesc, wDesc, in_layout, yType); } @@ -429,8 +430,12 @@ std::size_t ConvolutionDescriptor::GetWorkSpaceSize(ExecutionContext ctx, ctx.use_dynamic_solutions_only = findMode.IsDynamicHybrid(ctx); break; // Fall down to Normal Find. } - MIOPEN_LOG_I(solutions.front().workspace_size); - return solutions.front().workspace_size; + const auto id = solver::Id{solutions.front().solution_id}; + const auto& s = id.GetSolver(); + const auto workspace_size = s.GetWorkspaceSize(ctx, problem); + + MIOPEN_LOG_I(workspace_size); + return workspace_size; } size_t workspace_size; diff --git a/src/convolution_api.cpp b/src/convolution_api.cpp index 6929512e05..73f675e649 100644 --- a/src/convolution_api.cpp +++ b/src/convolution_api.cpp @@ -344,9 +344,9 @@ miopenGetConvolutionNdForwardOutputDim(miopenConvolutionDescriptor_t convDesc, auto out_desc = miopen::deref(convDesc).GetForwardOutputTensor( miopen::deref(inputTensorDesc), miopen::deref(filterDesc)); - miopen::deref(nDim) = out_desc.GetSize(); + miopen::deref(nDim) = out_desc.GetNumDims(); - for(int i = 0; i < out_desc.GetSize(); ++i) + for(unsigned i = 0; i < out_desc.GetNumDims(); ++i) { outputTensorDimA[i] = out_desc.GetLengths()[i]; } diff --git a/src/db.cpp b/src/db.cpp index 764a664f3a..d2e091a078 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -47,8 +47,9 @@ namespace miopen { -PlainTextDb::PlainTextDb(const std::string& filename_, bool is_system) - : filename(filename_), +PlainTextDb::PlainTextDb(DbKinds db_kind_, const std::string& filename_, bool is_system) + : db_kind(db_kind_), + filename(filename_), lock_file(LockFile::Get(LockFilePath(filename_).c_str())), warning_if_unreadable(is_system) { diff --git a/src/db_path.cpp.in b/src/db_path.cpp.in index d04157a7d2..8712218053 100644 --- a/src/db_path.cpp.in +++ b/src/db_path.cpp.in @@ -50,7 +50,7 @@ fs::path GetLibPath() if(dladdr(reinterpret_cast(miopenCreate), &info) != 0) { path = fs::canonical(fs::path{info.dli_fname}); - MIOPEN_LOG_I2(std::string("Lib Path: ") + path.string()); + MIOPEN_LOG_I2("Lib Path: " << path); if(path.empty()) return path; diff --git a/src/expanduser.cpp b/src/expanduser.cpp index 3f84bb5c2f..60f627ba1a 100644 --- a/src/expanduser.cpp +++ b/src/expanduser.cpp @@ -157,7 +157,7 @@ bool IsNetworkedFilesystem(const fs::path& path_) { if(fs::exists(path)) break; - MIOPEN_LOG_NQI2("Path does not exist: '" << path.string() << '\''); + MIOPEN_LOG_NQI2("Path does not exist: '" << path << '\''); path = path.parent_path(); if(path.empty()) break; @@ -167,12 +167,11 @@ bool IsNetworkedFilesystem(const fs::path& path_) if(rc != 0) { // NOLINTNEXTLINE (concurrency-mt-unsafe) - MIOPEN_LOG_NQE("statfs('" << path.string() << "') rc = " << rc << ", '" << strerror(errno) - << "'"); + MIOPEN_LOG_NQE("statfs('" << path << "') rc = " << rc << ", '" << strerror(errno) << "'"); return false; } - MIOPEN_LOG_NQI("Filesystem type at '" << path.string() << "' is: 0x" << std::hex << stat.f_type - << " '" << Stringize(stat.f_type) << '\''); + MIOPEN_LOG_NQI("Filesystem type at '" << path << "' is: 0x" << std::hex << stat.f_type << " '" + << Stringize(stat.f_type) << '\''); return IsNetworked(stat.f_type); } diff --git a/src/find_db.cpp b/src/find_db.cpp index d00052ad10..42cddf991f 100644 --- a/src/find_db.cpp +++ b/src/find_db.cpp @@ -65,7 +65,7 @@ std::string FindDbRecord_t::GetInstalledPathEmbed(Handle& handle, const auto suffix = GetSystemFindDbSuffix() + path_suffix; const auto filename = base_name + "." + suffix + ext; const auto file_path = root_path / filename; - if(miopen_data().find(filename + ".o") != miopen_data().end()) + if(miopen_data().find(make_object_file_name(filename).string()) != miopen_data().end()) { MIOPEN_LOG_I2("Found exact embedded find database file:" << filename); return file_path.string(); @@ -134,7 +134,7 @@ std::string FindDbRecord_t::GetInstalledPathFile(Handle& handle, const auto file_path = root_path / (base_name + "." + suffix + ext); if(fs::exists(file_path)) { - MIOPEN_LOG_I2("Found exact find database file: " + file_path.string()); + MIOPEN_LOG_I2("Found exact find database file: " << file_path); return file_path.string(); } else @@ -142,7 +142,7 @@ std::string FindDbRecord_t::GetInstalledPathFile(Handle& handle, MIOPEN_LOG_I2("inexact find database search"); if(fs::exists(root_path) && fs::is_directory(root_path)) { - MIOPEN_LOG_I2("Iterating over find db directory " << root_path.string()); + MIOPEN_LOG_I2("Iterating over find db directory " << root_path); std::vector all_files; std::vector contents; std::copy(fs::directory_iterator(root_path), @@ -150,8 +150,8 @@ std::string FindDbRecord_t::GetInstalledPathFile(Handle& handle, std::back_inserter(contents)); for(auto const& filepath : contents) { - const auto& fname = filepath.string(); - if(fs::is_regular_file(filepath) && EndsWith(fname, path_suffix + ".fdb.txt")) + if(fs::is_regular_file(filepath) && + filepath.extension() == path_suffix + ".fdb.txt") all_files.push_back(filepath); } diff --git a/src/fusion.cpp b/src/fusion.cpp index 034ffcb320..961f163b8f 100644 --- a/src/fusion.cpp +++ b/src/fusion.cpp @@ -45,11 +45,7 @@ #include #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #define MIOPEN_CHECK(x) \ if(x != miopenStatusSuccess) \ @@ -102,7 +98,7 @@ miopenStatus_t ConvBiasActivFusion(Handle& handle, float falpha1 = alpha1 != nullptr ? *(static_cast(alpha1)) : 1.0f; float falpha2 = alpha2 != nullptr ? *(static_cast(alpha2)) : 1.0f; - // if(z != nullptr || zDesc.GetSize() != 0) + // if(z != nullptr || zDesc.GetNumDims() != 0) // MIOPEN_THROW(miopenStatusNotImplemented, "The addition of z vector is not yet supported"); FusionPlanDescriptor fusePlanDesc{miopenVerticalFusion, xDesc}; OperatorArgs fusionArgs; diff --git a/src/gemm_v2.cpp b/src/gemm_v2.cpp index eb78bb11e7..f178421199 100644 --- a/src/gemm_v2.cpp +++ b/src/gemm_v2.cpp @@ -40,11 +40,7 @@ #pragma clang diagnostic ignored "-Wunused-macros" #define ROCBLAS_BETA_FEATURES_API 1 #pragma clang diagnostic pop -#if !defined(_WIN32) #include -#else -#include -#endif #if MIOPEN_ROCBLAS_VERSION_FLAT < 2045000 #include #else diff --git a/src/graphapi/convolution.cpp b/src/graphapi/convolution.cpp new file mode 100644 index 0000000000..d9735a138b --- /dev/null +++ b/src/graphapi/convolution.cpp @@ -0,0 +1,1033 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace graphapi { + +ConvolutionBuilder& ConvolutionBuilder::setCompType(miopenDataType_t compType) & noexcept +{ + mConvolution.mCompType = compType; + mCompTypeSet = true; + return *this; +} +ConvolutionBuilder& ConvolutionBuilder::setMode(miopenConvolutionMode_t mode) & noexcept +{ + mConvolution.mMode = mode; + mModeSet = true; + return *this; +} +ConvolutionBuilder& ConvolutionBuilder::setSpatialDims(int64_t spatialDims) & noexcept +{ + mConvolution.mSpatialDims = spatialDims; + mSpatialDimsSet = true; + return *this; +} +ConvolutionBuilder& ConvolutionBuilder::setDilations(const std::vector& dilations) & +{ + mConvolution.mDilations = dilations; + mDilationsSet = true; + return *this; +} +ConvolutionBuilder& ConvolutionBuilder::setDilations(std::vector&& dilations) & noexcept +{ + mConvolution.mDilations = std::move(dilations); + mDilationsSet = true; + return *this; +} +ConvolutionBuilder& +ConvolutionBuilder::setFilterStrides(const std::vector& filterStrides) & +{ + mConvolution.mFilterStrides = filterStrides; + mFilterStridesSet = true; + return *this; +} +ConvolutionBuilder& +ConvolutionBuilder::setFilterStrides(std::vector&& filterStrides) & noexcept +{ + mConvolution.mFilterStrides = std::move(filterStrides); + mFilterStridesSet = true; + return *this; +} +ConvolutionBuilder& ConvolutionBuilder::setPrePaddings(const std::vector& prePaddings) & +{ + mConvolution.mPrePaddings = prePaddings; + mPrePaddingsSet = true; + return *this; +} +ConvolutionBuilder& +ConvolutionBuilder::setPrePaddings(std::vector&& prePaddings) & noexcept +{ + mConvolution.mPrePaddings = std::move(prePaddings); + mPrePaddingsSet = true; + return *this; +} +ConvolutionBuilder& ConvolutionBuilder::setPostPaddings(const std::vector& postPaddings) & +{ + mConvolution.mPostPaddings = postPaddings; + mPostPaddingsSet = true; + return *this; +} +ConvolutionBuilder& +ConvolutionBuilder::setPostPaddings(std::vector&& postPaddings) & noexcept +{ + mConvolution.mPostPaddings = std::move(postPaddings); + mPostPaddingsSet = true; + return *this; +} + +bool ConvolutionBuilder::validate() const +{ + return mCompTypeSet && mModeSet && mSpatialDimsSet && mDilationsSet && mFilterStridesSet && + mPrePaddingsSet && mPostPaddingsSet && mConvolution.mSpatialDims >= 1 && + mConvolution.mDilations.size() == mConvolution.mSpatialDims && + mConvolution.mFilterStrides.size() == mConvolution.mSpatialDims && + mConvolution.mPrePaddings.size() == mConvolution.mSpatialDims && + mConvolution.mPostPaddings.size() == mConvolution.mSpatialDims && + miopen::all_of(mConvolution.mDilations, [](auto value) { return value > 0; }) && + miopen::all_of(mConvolution.mFilterStrides, [](auto value) { return value > 0; }) && + miopen::all_of(mConvolution.mPrePaddings, [](auto value) { return value >= 0; }) && + miopen::all_of(mConvolution.mPostPaddings, [](auto value) { return value >= 0; }); +} + +Convolution ConvolutionBuilder::build() const& +{ + if(!validate()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + return mConvolution; +} + +Convolution ConvolutionBuilder::build() && +{ + if(!validate()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + return std::move(mConvolution); +} + +void BackendConvolutionDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_CONVOLUTION_COMP_TYPE: + setCompType(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_CONV_MODE: + setMode(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS: + setSpatialDims(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_DILATIONS: + setDilations(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES: + setFilterStrides(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS: + setPrePaddings(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS: + setPostPaddings(attributeType, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mConvolution = std::move(mBuilder).build(); + mFinalized = true; +} + +void BackendConvolutionDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_CONVOLUTION_COMP_TYPE: + getCompType(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_CONV_MODE: + getMode(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS: + getSpatialDims(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_DILATIONS: + getDilations(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES: + getFilterStrides(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS: + getPrePaddings(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS: + getPostPaddings(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setCompType(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_DATA_TYPE && elementCount == 1) + { + mBuilder.setCompType(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setMode(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_CONVOLUTION_MODE && elementCount == 1) + { + mBuilder.setMode(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setSpatialDims(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && elementCount == 1) + { + mBuilder.setSpatialDims(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setDilations(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && elementCount > 0) + { + mBuilder.setDilations({static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount}); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setFilterStrides(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && elementCount > 0) + { + mBuilder.setFilterStrides({static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount}); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setPrePaddings(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && elementCount > 0) + { + mBuilder.setPrePaddings({static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount}); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::setPostPaddings(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && elementCount > 0) + { + mBuilder.setPostPaddings({static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount}); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getCompType(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_DATA_TYPE && requestedElementCount == 1) + { + *static_cast(arrayOfElements) = mConvolution.getCompType(); + *elementCount = 1; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getMode(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_CONVOLUTION_MODE && requestedElementCount == 1) + { + *static_cast(arrayOfElements) = mConvolution.getMode(); + *elementCount = 1; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getSpatialDims(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount == 1) + { + *static_cast(arrayOfElements) = mConvolution.getSpatialDims(); + *elementCount = 1; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getDilations(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount > 0) + { + const auto& dilations = mConvolution.getDilations(); + *elementCount = dilations.size(); + std::copy_n(dilations.begin(), + // WORKAROUND: building on Windows is failing due to conflicting definitions + // of std::min() between the MSVC standard library and HIP Clang wrappers. + *elementCount < requestedElementCount ? *elementCount : requestedElementCount, + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getFilterStrides(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount > 0) + { + const auto& strides = mConvolution.getFilterStrides(); + *elementCount = strides.size(); + std::copy_n(strides.begin(), + // WORKAROUND: building on Windows is failing due to conflicting definitions + // of std::min() between the MSVC standard library and HIP Clang wrappers. + *elementCount < requestedElementCount ? *elementCount : requestedElementCount, + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getPrePaddings(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount > 0) + { + const auto& pads = mConvolution.getPrePaddings(); + *elementCount = pads.size(); + std::copy_n(pads.begin(), + // WORKAROUND: building on Windows is failing due to conflicting definitions + // of std::min() between the MSVC standard library and HIP Clang wrappers. + *elementCount < requestedElementCount ? *elementCount : requestedElementCount, + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendConvolutionDescriptor::getPostPaddings(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount > 0) + { + const auto& postPads = mConvolution.getPostPaddings(); + *elementCount = postPads.size(); + std::copy_n(postPads.begin(), + // WORKAROUND: building on Windows is failing due to conflicting definitions + // of std::min() between the MSVC standard library and HIP Clang wrappers. + *elementCount < requestedElementCount ? *elementCount : requestedElementCount, + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::setConvolution( + miopenBackendAttributeType_t attributeType, int64_t elementCount, void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + mConvolutionDescriptor = deref(static_cast(arrayOfElements)); + auto& baseDescr = deref(mConvolutionDescriptor); + auto& convDescr = dynamic_cast(baseDescr); + getBuilder().setConvolution(convDescr.getConvolution()); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::setX(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + mXDescriptor = deref(static_cast(arrayOfElements)); + auto& baseDescr = deref(mXDescriptor); + auto& tensDescr = dynamic_cast(baseDescr); + getBuilder().setX(tensDescr.getTensor()); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::setW(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + mWDescriptor = deref(static_cast(arrayOfElements)); + auto& baseDescr = deref(mWDescriptor); + auto& tensDescr = dynamic_cast(baseDescr); + getBuilder().setW(tensDescr.getTensor()); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::setY(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + mYDescriptor = deref(static_cast(arrayOfElements)); + auto& baseDescr = deref(mYDescriptor); + auto& tensDescr = dynamic_cast(baseDescr); + getBuilder().setY(tensDescr.getTensor()); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::setAlpha(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_DOUBLE && elementCount > 0) + { + getBuilder().setAlpha(deref(static_cast(arrayOfElements))); + } + else if(attributeType == MIOPEN_TYPE_FLOAT && elementCount > 0) + { + getBuilder().setAlpha(deref(static_cast(arrayOfElements))); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::setBeta(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_DOUBLE && elementCount > 0) + { + getBuilder().setBeta(deref(static_cast(arrayOfElements))); + } + else if(attributeType == MIOPEN_TYPE_FLOAT && elementCount > 0) + { + getBuilder().setBeta(deref(static_cast(arrayOfElements))); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::getConvolution( + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mConvolutionDescriptor; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::getX(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mXDescriptor; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::getW(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mWDescriptor; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::getY(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mYDescriptor; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::getAlpha(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_DOUBLE && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = getOperationConvolution().getAlpha(); + return; + } + else if(attributeType == MIOPEN_TYPE_FLOAT && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = getOperationConvolution().getAlpha(); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionDescriptor::getBeta(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(attributeType == MIOPEN_TYPE_DOUBLE && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = getOperationConvolution().getBeta(); + return; + } + else if(attributeType == MIOPEN_TYPE_FLOAT && requestedElementCount > 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = getOperationConvolution().getBeta(); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionForwardDescriptor::setAttribute( + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC: + setConvolution(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X: + setX(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W: + setW(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y: + setY(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA: + setAlpha(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA: + setBeta(attributeType, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionForwardDescriptor::finalize() +{ + if(mFinalized || !deref(mConvolutionDescriptor).isFinalized() || + !deref(mXDescriptor).isFinalized() || !deref(mYDescriptor).isFinalized() || + !deref(mWDescriptor).isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + mOperation = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationConvolutionForwardDescriptor::getAttribute( + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC: + getConvolution(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X: + getX(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W: + getW(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y: + getY(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA: + getAlpha(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA: + getBeta(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OpNode* BackendOperationConvolutionForwardDescriptor::getOperation() +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusInternalError); + } + return &mOperation; +} + +OperationConvolutionBuilder& BackendOperationConvolutionForwardDescriptor::getBuilder() +{ + return mBuilder; +} + +OperationConvolution& BackendOperationConvolutionForwardDescriptor::getOperationConvolution() +{ + return mOperation; +} + +void BackendOperationConvolutionBackwardDataDescriptor::setAttribute( + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC: + setConvolution(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX: + setX(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W: + setW(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY: + setY(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA: + setAlpha(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA: + setBeta(attributeType, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionBackwardDataDescriptor::finalize() +{ + if(mFinalized || !deref(mConvolutionDescriptor).isFinalized() || + !deref(mXDescriptor).isFinalized() || !deref(mYDescriptor).isFinalized() || + !deref(mWDescriptor).isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + mOperation = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationConvolutionBackwardDataDescriptor::getAttribute( + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC: + getConvolution(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX: + getX(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W: + getW(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY: + getY(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA: + getAlpha(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA: + getBeta(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OpNode* BackendOperationConvolutionBackwardDataDescriptor::getOperation() +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusInternalError); + } + return &mOperation; +} + +OperationConvolutionBuilder& BackendOperationConvolutionBackwardDataDescriptor::getBuilder() +{ + return mBuilder; +} + +OperationConvolution& BackendOperationConvolutionBackwardDataDescriptor::getOperationConvolution() +{ + return mOperation; +} + +void BackendOperationConvolutionBackwardFilterDescriptor::setAttribute( + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC: + setConvolution(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X: + setX(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW: + setW(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY: + setY(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA: + setAlpha(attributeType, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA: + setBeta(attributeType, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationConvolutionBackwardFilterDescriptor::finalize() +{ + if(mFinalized || !deref(mConvolutionDescriptor).isFinalized() || + !deref(mXDescriptor).isFinalized() || !deref(mYDescriptor).isFinalized() || + !deref(mWDescriptor).isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + mOperation = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationConvolutionBackwardFilterDescriptor::getAttribute( + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC: + getConvolution(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X: + getX(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW: + getW(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY: + getY(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA: + getAlpha(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + case MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA: + getBeta(attributeType, requestedElementCount, elementCount, arrayOfElements); + return; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OpNode* BackendOperationConvolutionBackwardFilterDescriptor::getOperation() +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusInternalError); + } + return &mOperation; +} + +OperationConvolutionBuilder& BackendOperationConvolutionBackwardFilterDescriptor::getBuilder() +{ + return mBuilder; +} + +OperationConvolution& BackendOperationConvolutionBackwardFilterDescriptor::getOperationConvolution() +{ + return mOperation; +} + +} // namespace graphapi + +} // namespace miopen diff --git a/src/graphapi/graphapi.cpp b/src/graphapi/graphapi.cpp new file mode 100644 index 0000000000..534f4d25ac --- /dev/null +++ b/src/graphapi/graphapi.cpp @@ -0,0 +1,282 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +extern "C" miopenStatus_t +miopenBackendCreateDescriptor(miopenBackendDescriptorType_t descriptorType, + miopenBackendDescriptor_t* descriptor) +{ + MIOPEN_LOG_FUNCTION(descriptorType, descriptor); + return miopen::try_([&] { + auto& outputDescriptor = miopen::deref(descriptor); + + switch(descriptorType) + { + /* This part is a common place of changes of about 25 PRs and merge conflicts arise heavily + * here. Turn off clang-format to keep each line unique to simplify resolving of conflicts. + * + * TODO: Turn on clang-format when active phase of development is finished. + */ + // clang-format off + case MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendConvolutionDescriptor(); break; + + case MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationConvolutionForwardDescriptor(); break; + + case MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationConvolutionBackwardFilterDescriptor(); break; + + case MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationConvolutionBackwardDataDescriptor(); break; + + case MIOPEN_BACKEND_OPERATION_POINTWISE_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationPointwiseDescriptor(); break; + + case MIOPEN_BACKEND_OPERATION_REDUCTION_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationReductionDescriptor(); break; + + case MIOPEN_BACKEND_OPERATION_RNG_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationRngDescriptor(); break; + + case MIOPEN_BACKEND_OPERATIONGRAPH_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationGraphDescriptor(); break; + + case MIOPEN_BACKEND_POINTWISE_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendPointwiseDescriptor(); break; + + case MIOPEN_BACKEND_REDUCTION_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendReductionDescriptor(); break; + + case MIOPEN_BACKEND_RNG_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendRngDescriptor(); break; + + case MIOPEN_BACKEND_TENSOR_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendTensorDescriptor(); break; + + case MIOPEN_BACKEND_VARIANT_PACK_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendVariantPackDescriptor(); break; + + case MIOPEN_BACKEND_MATMUL_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendMatmulDescriptor(); + break; + + case MIOPEN_BACKEND_OPERATION_MATMUL_DESCRIPTOR: + outputDescriptor = new miopen::graphapi::BackendOperationMatmulDescriptor(); + break; + + default: MIOPEN_THROW(miopenStatusUnsupportedOp); + // clang-format on + } + }); +} + +extern "C" miopenStatus_t miopenBackendSetAttribute(miopenBackendDescriptor_t descriptor, + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + MIOPEN_LOG_FUNCTION(attributeName, attributeType, elementCount); + + if(arrayOfElements == nullptr) + { + return miopenStatusBadParm; + } + + return miopen::try_( + [&] { + auto& theDescriptor = miopen::deref(descriptor); + theDescriptor.setAttribute(attributeName, attributeType, elementCount, arrayOfElements); + }, + false); +} + +extern "C" miopenStatus_t miopenBackendFinalize(miopenBackendDescriptor_t descriptor) +{ + return miopen::try_( + [&] { + auto& theDescriptor = miopen::deref(descriptor); + theDescriptor.finalize(); + }, + false); +} + +extern "C" miopenStatus_t miopenBackendGetAttribute(miopenBackendDescriptor_t descriptor, + miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(elementCount == nullptr || arrayOfElements == nullptr) + { + return miopenStatusBadParm; + } + + return miopen::try_( + [&] { + auto& theDescriptor = miopen::deref(descriptor); + theDescriptor.getAttribute( + attributeName, attributeType, requestedElementCount, elementCount, arrayOfElements); + }, + false); +} + +extern "C" miopenStatus_t miopenBackendExecute(miopenHandle_t handle, + miopenBackendDescriptor_t executionPlan, + miopenBackendDescriptor_t variantPack) +{ + return miopen::try_( + [&] { + auto& theDescriptor = miopen::deref(executionPlan); + theDescriptor.execute(handle, variantPack); + }, + false); +} + +extern "C" miopenStatus_t miopenBackendDestroyDescriptor(miopenBackendDescriptor_t descriptor) +{ + return miopen::try_([&] { miopen_destroy_object(descriptor); }, false); +} + +template +static void initializeBackendDescriptor(void* descriptor, std::size_t sizeInBytes) +{ + void* address = descriptor; + if(std::align( + alignof(BackendDescriptorType), sizeof(BackendDescriptorType), address, sizeInBytes) != + nullptr && + address == descriptor) + { + new(descriptor) BackendDescriptorType(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +extern "C" miopenStatus_t miopenBackendInitialize(miopenBackendDescriptor_t descriptor, + miopenBackendDescriptorType_t descriptorType, + size_t sizeInBytes) +{ + MIOPEN_LOG_FUNCTION(descriptorType, sizeInBytes); + + if(descriptor == nullptr) + { + return miopenStatusBadParm; + } + + return miopen::try_([&] { + switch(descriptorType) + { + /* This part is a common place of changes of about 25 PRs and merge conflicts arise heavily + * here. Turn off clang-format to keep each line unique to simplify resolving of conflicts. + * + * TODO: Turn on clang-format when active phase of development is finished. + */ + // clang-format off + case MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_MATMUL_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_POINTWISE_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_REDUCTION_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATION_RNG_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_OPERATIONGRAPH_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_POINTWISE_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_REDUCTION_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_RNG_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_TENSOR_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_MATMUL_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + case MIOPEN_BACKEND_VARIANT_PACK_DESCRIPTOR: + initializeBackendDescriptor(descriptor, sizeInBytes); break; + + default: MIOPEN_THROW(miopenStatusUnsupportedOp); + // clang-format on + } + }); +} + +namespace miopen { + +namespace graphapi { + +BackendDescriptor::~BackendDescriptor() {} + +void BackendDescriptor::execute([[maybe_unused]] miopenHandle_t handle, + [[maybe_unused]] miopenBackendDescriptor_t variantPack) +{ + MIOPEN_THROW(miopenStatusBadParm); +} + +OpNode* BackendDescriptor::getOperation() { return nullptr; } + +} // namespace graphapi + +} // namespace miopen diff --git a/src/graphapi/matmul.cpp b/src/graphapi/matmul.cpp new file mode 100644 index 0000000000..1454281c7a --- /dev/null +++ b/src/graphapi/matmul.cpp @@ -0,0 +1,441 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { +namespace graphapi { + +Matmul MatmulBuilder::build() const +{ + if(!mComputeTypeSet) + MIOPEN_THROW(miopenStatusBadParm); + return mMatmul; +} + +void BackendMatmulDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_MATMUL_COMP_TYPE: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && elementCount == 1) + { + mBuilder.setComputeType(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendMatmulDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mMatmul = mBuilder.build(); + mFinalized = true; +} + +void BackendMatmulDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_MATMUL_COMP_TYPE: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mMatmul.getComputeType(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OperationMatmulBuilder& OperationMatmulBuilder::setA(Tensor* A) +{ + mOperationMatmul.mA = checkPtr(A); + if(mOperationMatmul.mA->getDimensions().size() < 2) + MIOPEN_THROW(miopenStatusBadParm); + mASet = true; + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setB(Tensor* B) +{ + mOperationMatmul.mB = checkPtr(B); + if(mOperationMatmul.mB->getDimensions().size() < 2) + MIOPEN_THROW(miopenStatusBadParm); + mBSet = true; + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setC(Tensor* C) +{ + mOperationMatmul.mC = checkPtr(C); + if(mOperationMatmul.mC->getDimensions().size() < 2) + MIOPEN_THROW(miopenStatusBadParm); + mCSet = true; + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setBatchCount(int64_t count) +{ + mOperationMatmul.mBatchCount = count; + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setGemmMOverride(Tensor* overrideTensor) +{ + mOperationMatmul.mGemmMOverride = checkPtr(overrideTensor); + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setGemmNOverride(Tensor* overrideTensor) +{ + mOperationMatmul.mGemmNOverride = checkPtr(overrideTensor); + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setGemmKOverride(Tensor* overrideTensor) +{ + mOperationMatmul.mGemmKOverride = checkPtr(overrideTensor); + return *this; +}; +OperationMatmulBuilder& OperationMatmulBuilder::setMatmulDescriptor(Matmul* mMatmul) +{ + mOperationMatmul.mMatmul = checkPtr(mMatmul); + mMatmulSet = true; + return *this; +} + +OperationMatmul OperationMatmulBuilder::build() +{ + if(!mASet || !mBSet || !mCSet || !mMatmulSet) + MIOPEN_THROW(miopenStatusBadParm); + + auto& aDimensions = mOperationMatmul.mA->getDimensions(); + auto& bDimensions = mOperationMatmul.mB->getDimensions(); + auto& cDimensions = mOperationMatmul.mC->getDimensions(); + + int aDimensionsCount = aDimensions.size(); + int bDimensionsCount = bDimensions.size(); + int cDimensionsCount = cDimensions.size(); + + if(cDimensionsCount != std::max(aDimensionsCount, bDimensionsCount)) + MIOPEN_THROW(miopenStatusBadParm); + + int Am = aDimensions[aDimensionsCount - 2]; + int An = aDimensions[aDimensionsCount - 1]; + + int Bn = bDimensions[bDimensionsCount - 2]; + int Bk = bDimensions[bDimensionsCount - 1]; + + int Cm = cDimensions[cDimensionsCount - 2]; + int Ck = cDimensions[cDimensionsCount - 1]; + + if(Am != Cm || An != Bn || Bk != Ck) + MIOPEN_THROW(miopenStatusBadParm); + + auto correctBroadcastedDims = [](int dim1, int dim2, int dimOut) -> bool { + if(dim1 == dim2 && dim2 == dimOut) + return true; + if(dim1 == 1) + return dimOut == dim2; + if(dim2 == 1) + return dimOut == dim1; + return false; + }; + + auto lengthDiff = + std::max(aDimensionsCount, bDimensionsCount) - std::min(aDimensionsCount, bDimensionsCount); + + auto& longestDims = aDimensionsCount > bDimensionsCount ? aDimensions : bDimensions; + + // For tensors (j×1×n×m) and (k×m×p) second tensor will be virtually extended to (1*k×m×p) + // + for(int i = 0; i < lengthDiff; i++) + { + if(!correctBroadcastedDims(1, longestDims[i], cDimensions[i])) + MIOPEN_THROW(miopenStatusBadParm); + } + // Last 2 dimensions are not batch dimensions + // + for(int i = lengthDiff; i < cDimensionsCount - 2; i++) + { + if(!correctBroadcastedDims(aDimensions[i], bDimensions[i], cDimensions[i])) + MIOPEN_THROW(miopenStatusBadParm); + } + return mOperationMatmul; +} + +void BackendOperationMatmulDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mMatmul = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationMatmulDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_MATMUL_ADESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mA; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_BDESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mB; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_CDESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mC; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mMatmuDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_GEMM_M_OVERRIDE_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mGemmMOverride; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_GEMM_N_OVERRIDE_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mGemmNOverride; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_GEMM_K_OVERRIDE_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mGemmKOverride; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_IRREGULARLY_STRIDED_BATCH_COUNT: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mMatmul.getBatchCount(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationMatmulDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using TensorSetter = OperationMatmulBuilder& (OperationMatmulBuilder::*)(Tensor*); + + auto callTensorSetter = [=](TensorSetter setter, miopenBackendDescriptor_t& outApiDescriptor) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendTensorDescriptor& tensorDescriptor = + dynamic_cast(backendDescriptor); + (mBuilder.*setter)(tensorDescriptor.getTensor()); + outApiDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_MATMUL_ADESC: + callTensorSetter(&OperationMatmulBuilder::setA, mA); + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_BDESC: + callTensorSetter(&OperationMatmulBuilder::setB, mB); + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_CDESC: + callTensorSetter(&OperationMatmulBuilder::setC, mC); + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + BackendMatmulDescriptor& matmulDescriptor = + dynamic_cast(backendDescriptor); + mBuilder.setMatmulDescriptor(matmulDescriptor.getMatmul()); + mMatmuDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_GEMM_M_OVERRIDE_DESC: + callTensorSetter(&OperationMatmulBuilder::setGemmMOverride, mGemmMOverride); + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_GEMM_N_OVERRIDE_DESC: + callTensorSetter(&OperationMatmulBuilder::setGemmNOverride, mGemmNOverride); + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_GEMM_K_OVERRIDE_DESC: + callTensorSetter(&OperationMatmulBuilder::setGemmKOverride, mGemmKOverride); + break; + + case MIOPEN_ATTR_OPERATION_MATMUL_IRREGULARLY_STRIDED_BATCH_COUNT: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount == 1) + { + mBuilder.setBatchCount(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +} // namespace graphapi +} // namespace miopen diff --git a/src/graphapi/opgraph.cpp b/src/graphapi/opgraph.cpp new file mode 100644 index 0000000000..92e04683ae --- /dev/null +++ b/src/graphapi/opgraph.cpp @@ -0,0 +1,451 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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 + +namespace miopen { +namespace graphapi { + +OpNode::~OpNode() = default; + +void OpGraphBuilder::setHandle(miopenHandle_t handle) { mHandle = checkPtr(handle); } + +OpGraph OpGraphBuilder::build() && +{ + if(mHandle == nullptr || mNodes.empty()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + OpGraph graph; + + graph.mHandle = mHandle; + + // key = tensor ptr, value = vec. of dest nodes + /// \todo might eventually move this state to the graph class during build() + std::unordered_map e_map; + + for(OpNode* n : mNodes) + { + + for(Tensor* i : n->getInTensors()) + { + auto [iter, _ignore] = + e_map.try_emplace(i, EdgeInfo{}); // add empty EdgeInfo if not present + + iter->second.mDests.emplace_back(n); + } + + for(Tensor* o : n->getOutTensors()) + { + auto [iter, _ignore] = + e_map.try_emplace(o, EdgeInfo{}); // add empty EdgeInfo if not present + + assert(iter->second.mSrc == nullptr); + iter->second.mSrc = n; + } + } + + graph.initNodes(std::move(mNodes)); + + for(const auto& [tens_ptr, edge_info] : e_map) + { + MIOPEN_THROW_IF(edge_info.mSrc == nullptr && edge_info.mDests.empty(), + "Invalid state with null src node and empty dest nodes"); + + if(edge_info.mSrc != nullptr && !edge_info.mDests.empty()) + { + for(const auto& d : edge_info.mDests) + { + graph.addEdge(edge_info.mSrc, tens_ptr, d); + } + } + else if(edge_info.mSrc == nullptr) + { + for(const auto& d : edge_info.mDests) + { + graph.addEdgeFromSrc(d, tens_ptr); + } + } + else if(edge_info.mDests.empty()) + { + graph.addEdgeToSink(edge_info.mSrc, tens_ptr); + } + } + + return graph; +} + +VecOfPaths OpGraph::getAllPaths() const +{ + // TODO(Amber): does not check for cycles. Use DFS to first check for cycles + // at construction time perhaps. + VecOfPaths all_paths; + + std::deque paths_to_explore; + paths_to_explore.emplace_back(Path{mSrcNode.get()}); + + while(!paths_to_explore.empty()) + { + Path path = paths_to_explore.front(); + paths_to_explore.pop_front(); + + assert(!path.empty()); + const OpNode* last_node = path.back(); + assert(last_node); + if(last_node->getOutEdges().empty()) + { + // all paths should terminate at the sink + assert(last_node == mSinkNode.get()); + all_paths.emplace_back(std::move(path)); + } + else + { + for(const auto& [dst, tens_ptr] : last_node->getOutEdges()) + { + Path newPath{path}; + newPath.emplace_back(dst); + paths_to_explore.emplace_back(std::move(newPath)); + } + } + } // end while + + return all_paths; +} + +std::string pathToStr(const Path& path) +{ + std::ostringstream oss; + for(const OpNode* n : path) + { + oss << n->signName() << ","; + } + return oss.str(); +} + +namespace internal { + +using MapSizeToPathVec = std::unordered_map; + +bool checkSameNodesByName(const OpGraph& left, const OpGraph& right) +{ + auto l_names = left.getNodeNames(); + auto r_names = right.getNodeNames(); + if(l_names.size() != r_names.size()) + { + return false; + } + + std::sort(l_names.begin(), l_names.end()); + std::sort(r_names.begin(), r_names.end()); + + return l_names == r_names; +} + +bool checkSameDegreeVecs(const OpGraph& left, const OpGraph& right) +{ + auto l_degs = left.getInOutDegrees(); + auto r_degs = right.getInOutDegrees(); + + /* + auto sort_deg_vec = [] (auto& deg_vec) { + + std::sort(deg_vec.begin(), deg_vec.end(), + [] (const auto& left, const auto& right) { + if (left.first == right.first) { + return left.second < right.second; + } + return left.first < right.first; + }); + + }; + sort_deg_vec(l_degs); + sort_deg_vec(r_degs); + */ + std::sort(l_degs.begin(), l_degs.end()); + std::sort(r_degs.begin(), r_degs.end()); + return l_degs == r_degs; +} + +auto groupBySize(VecOfPaths&& all_paths) +{ + MapSizeToPathVec paths_by_size; + + for(auto& p : all_paths) + { + auto [it, _ignore] = paths_by_size.emplace(p.size(), VecOfPaths{}); + it->second.emplace_back(std::move(p)); + } + + return paths_by_size; +} + +/* +auto sumPathSizes(const VecOfPaths& all_paths) { + size_t ret = 0; + for (const auto& p: all_paths) { + ret += p.size(); + } + return ret; +} +*/ + +bool checkSamePathVecs(const VecOfPaths& left, const VecOfPaths& right) +{ + if(left.size() != right.size()) + { + return false; + } + + using VecOfStr = std::vector; + + auto pathvec_to_strvec = [](const VecOfPaths& pathvec) { + VecOfStr ret; + for(const Path& path : pathvec) + { + ret.emplace_back(pathToStr(path)); + } + return ret; + }; + + VecOfStr l_paths_as_str = pathvec_to_strvec(left); + VecOfStr r_paths_as_str = pathvec_to_strvec(right); + + std::sort(l_paths_as_str.begin(), l_paths_as_str.end()); + std::sort(r_paths_as_str.begin(), r_paths_as_str.end()); + + return l_paths_as_str == r_paths_as_str; +} + +bool checkSamePaths(const OpGraph& left, const OpGraph& right) +{ + + auto l_paths = left.getAllPaths(); + auto r_paths = right.getAllPaths(); + + if(l_paths.size() != r_paths.size()) + { + return false; + } + + auto l_paths_by_sz = groupBySize(std::move(l_paths)); + auto r_paths_by_sz = groupBySize(std::move(r_paths)); + + auto get_keys = [](const MapSizeToPathVec& paths_by_size) { + std::vector keys{}; + for(const auto& [k, v] : paths_by_size) + { + keys.emplace_back(k); + } + return keys; + }; + + auto l_keys = get_keys(l_paths_by_sz); + auto r_keys = get_keys(r_paths_by_sz); + + if(l_keys != r_keys) + { + return false; + } + + for(size_t k : l_keys) + { + if(!checkSamePathVecs(l_paths_by_sz[k], r_paths_by_sz[k])) + { + return false; + } + } + + return true; +} + +} // end namespace internal + +bool isIsomorphic(const OpGraph& left, const OpGraph& right) +{ + if(left.numNodes() != right.numNodes()) + { + return false; + } + + if(left.numEdges() != right.numEdges()) + { + return false; + } + + if(!internal::checkSameNodesByName(left, right)) + { + return false; + } + + if(!internal::checkSameDegreeVecs(left, right)) + { + return false; + } + + if(!internal::checkSamePaths(left, right)) + { + return false; + } + + return true; +} + +void BackendOperationGraphDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATIONGRAPH_HANDLE: + if(attributeType == MIOPEN_TYPE_HANDLE && elementCount == 1) + { + mBuilder.setHandle(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATIONGRAPH_OPS: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount > 0) + { + std::vector descriptors; + descriptors.reserve(elementCount); + std::vector nodes; + nodes.reserve(elementCount); + + std::for_each_n(static_cast(arrayOfElements), + elementCount, + [&descriptors, &nodes](miopenBackendDescriptor_t apiDescriptor) { + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + if(backendDescriptor.isFinalized()) + { + descriptors.push_back(apiDescriptor); + nodes.push_back(backendDescriptor.getOperation()); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }); + + if(!internal::noRepetitions(nodes)) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mBuilder.setNodes(std::move(nodes)); + mOps = std::move(descriptors); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationGraphDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + mOpGraph = std::move(mBuilder).build(); + mFinalized = true; +} + +void BackendOperationGraphDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATIONGRAPH_HANDLE: + if(attributeType == MIOPEN_TYPE_HANDLE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mOpGraph.getHandle(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATIONGRAPH_OPS: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount >= 0) + { + *elementCount = mOps.size(); + std::copy_n(mOps.cbegin(), + std::min(*elementCount, requestedElementCount), + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATIONGRAPH_ENGINE_GLOBAL_COUNT: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mOpGraph.getEngines().size(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +} // end namespace graphapi +} // end namespace miopen diff --git a/src/graphapi/pointwise.cpp b/src/graphapi/pointwise.cpp new file mode 100644 index 0000000000..8315e6b70f --- /dev/null +++ b/src/graphapi/pointwise.cpp @@ -0,0 +1,1042 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace graphapi { + +Pointwise PointwiseBuilder::build() +{ + if(!mModeSet || !mMathPrecisionSet) + { + MIOPEN_THROW(miopenStatusBadParm); + } + return mPointwise; +} + +void BackendPointwiseDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using Setter = PointwiseBuilder& (PointwiseBuilder::*)(Pointwise::FpAttribute value); + + auto setFloatOrDouble = [=](Setter setter) { + if(attributeType == MIOPEN_TYPE_FLOAT && elementCount == 1) + { + (mBuilder.*setter)(*static_cast(arrayOfElements)); + } + else if(attributeType == MIOPEN_TYPE_DOUBLE && elementCount == 1) + { + (mBuilder.*setter)(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_POINTWISE_MODE: + if(attributeType == MIOPEN_TYPE_POINTWISE_MODE && elementCount == 1) + { + mBuilder.setMode(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_POINTWISE_MATH_PREC: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && elementCount == 1) + { + mBuilder.setMathPrecision(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_POINTWISE_NAN_PROPAGATION: + if(attributeType == MIOPEN_TYPE_NAN_PROPOGATION && elementCount == 1) + { + mBuilder.setNanPropagation(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP: + setFloatOrDouble(&PointwiseBuilder::setReluLowerClip); + break; + + case MIOPEN_ATTR_POINTWISE_RELU_UPPER_CLIP: + setFloatOrDouble(&PointwiseBuilder::setReluUpperClip); + break; + + case MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP_SLOPE: + setFloatOrDouble(&PointwiseBuilder::setReluLowerClipSlope); + break; + + case MIOPEN_ATTR_POINTWISE_ELU_ALPHA: setFloatOrDouble(&PointwiseBuilder::setEluAlpha); break; + + case MIOPEN_ATTR_POINTWISE_SOFTPLUS_BETA: + setFloatOrDouble(&PointwiseBuilder::setSoftPlusBeta); + break; + + case MIOPEN_ATTR_POINTWISE_SWISH_BETA: setFloatOrDouble(&PointwiseBuilder::setSwishBeta); break; + + case MIOPEN_ATTR_POINTWISE_AXIS: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount == 1) + { + mBuilder.setAxis(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendPointwiseDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mPointwise = mBuilder.build(); + mFinalized = true; +} + +void BackendPointwiseDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using Getter = Pointwise::FpAttribute (Pointwise::*)() const; + + auto getFloatOrDouble = [=](Getter getter) { + if(attributeType == MIOPEN_TYPE_FLOAT && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = std::get((mPointwise.*getter)()); + } + else if(attributeType == MIOPEN_TYPE_DOUBLE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = std::get((mPointwise.*getter)()); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_POINTWISE_MODE: + if(attributeType == MIOPEN_TYPE_POINTWISE_MODE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mPointwise.getMode(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_POINTWISE_MATH_PREC: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mPointwise.getMathPrecision(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_POINTWISE_NAN_PROPAGATION: + if(attributeType == MIOPEN_TYPE_NAN_PROPOGATION && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mPointwise.getNanPropagation(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP: + getFloatOrDouble(&Pointwise::getReluLowerClip); + break; + + case MIOPEN_ATTR_POINTWISE_RELU_UPPER_CLIP: + getFloatOrDouble(&Pointwise::getReluUpperClip); + break; + + case MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP_SLOPE: + getFloatOrDouble(&Pointwise::getReluLowerClipSlope); + break; + + case MIOPEN_ATTR_POINTWISE_ELU_ALPHA: getFloatOrDouble(&Pointwise::getEluAlpha); break; + + case MIOPEN_ATTR_POINTWISE_SOFTPLUS_BETA: getFloatOrDouble(&Pointwise::getSoftPlusBeta); break; + + case MIOPEN_ATTR_POINTWISE_SWISH_BETA: getFloatOrDouble(&Pointwise::getSwishBeta); break; + + case MIOPEN_ATTR_POINTWISE_AXIS: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mPointwise.getAxis(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +const std::string& OperationPointwise::signName() const +{ + switch(mPointwise->getMode()) + { + case MIOPEN_POINTWISE_ADD: { + static const std::string name = "OP_POINTWISE:ADD"; + return name; + } + case MIOPEN_POINTWISE_ADD_SQUARE: { + static const std::string name = "OP_POINTWISE:ADD_SQUARE"; + return name; + } + case MIOPEN_POINTWISE_DIV: { + static const std::string name = "OP_POINTWISE:DIV"; + return name; + } + case MIOPEN_POINTWISE_MAX: { + static const std::string name = "OP_POINTWISE:MAX"; + return name; + } + case MIOPEN_POINTWISE_MIN: { + static const std::string name = "OP_POINTWISE:MIN"; + return name; + } + case MIOPEN_POINTWISE_MOD: { + static const std::string name = "OP_POINTWISE:MOD"; + return name; + } + case MIOPEN_POINTWISE_MUL: { + static const std::string name = "OP_POINTWISE:MUL"; + return name; + } + case MIOPEN_POINTWISE_POW: { + static const std::string name = "OP_POINTWISE:POW"; + return name; + } + case MIOPEN_POINTWISE_SUB: { + static const std::string name = "OP_POINTWISE:SUB"; + return name; + } + case MIOPEN_POINTWISE_ABS: { + static const std::string name = "OP_POINTWISE:ABS"; + return name; + } + case MIOPEN_POINTWISE_CEIL: { + static const std::string name = "OP_POINTWISE:CEIL"; + return name; + } + case MIOPEN_POINTWISE_COS: { + static const std::string name = "OP_POINTWISE:COS"; + return name; + } + case MIOPEN_POINTWISE_EXP: { + static const std::string name = "OP_POINTWISE:EXP"; + return name; + } + case MIOPEN_POINTWISE_FLOOR: { + static const std::string name = "OP_POINTWISE:FLOOR"; + return name; + } + case MIOPEN_POINTWISE_LOG: { + static const std::string name = "OP_POINTWISE:LOG"; + return name; + } + case MIOPEN_POINTWISE_NEG: { + static const std::string name = "OP_POINTWISE:NEG"; + return name; + } + case MIOPEN_POINTWISE_RSQRT: { + static const std::string name = "OP_POINTWISE:RSQRT"; + return name; + } + case MIOPEN_POINTWISE_SIN: { + static const std::string name = "OP_POINTWISE:SIN"; + return name; + } + case MIOPEN_POINTWISE_SQRT: { + static const std::string name = "OP_POINTWISE:SQRT"; + return name; + } + case MIOPEN_POINTWISE_TAN: { + static const std::string name = "OP_POINTWISE:TAN"; + return name; + } + case MIOPEN_POINTWISE_ERF: { + static const std::string name = "OP_POINTWISE:ERF"; + return name; + } + case MIOPEN_POINTWISE_IDENTITY: { + static const std::string name = "OP_POINTWISE:IDENTITY"; + return name; + } + case MIOPEN_POINTWISE_RELU_FWD: { + static const std::string name = "OP_POINTWISE:RELU_FWD"; + return name; + } + case MIOPEN_POINTWISE_TANH_FWD: { + static const std::string name = "OP_POINTWISE:TANH_FWD"; + return name; + } + case MIOPEN_POINTWISE_SIGMOID_FWD: { + static const std::string name = "OP_POINTWISE:SIGMOID_FWD"; + return name; + } + case MIOPEN_POINTWISE_ELU_FWD: { + static const std::string name = "OP_POINTWISE:ELU_FWD"; + return name; + } + case MIOPEN_POINTWISE_GELU_FWD: { + static const std::string name = "OP_POINTWISE:GELU_FWD"; + return name; + } + case MIOPEN_POINTWISE_SOFTPLUS_FWD: { + static const std::string name = "OP_POINTWISE:SOFTPLUS_FWD"; + return name; + } + case MIOPEN_POINTWISE_SWISH_FWD: { + static const std::string name = "OP_POINTWISE:SWISH_FWD"; + return name; + } + case MIOPEN_POINTWISE_GELU_APPROX_TANH_FWD: { + static const std::string name = "OP_POINTWISE:APPROX_TANH_FWD"; + return name; + } + case MIOPEN_POINTWISE_RELU_BWD: { + static const std::string name = "OP_POINTWISE:RELU_BWD"; + return name; + } + case MIOPEN_POINTWISE_TANH_BWD: { + static const std::string name = "OP_POINTWISE:TANH_BWD"; + return name; + } + case MIOPEN_POINTWISE_SIGMOID_BWD: { + static const std::string name = "OP_POINTWISE:SIGMOID_BWD"; + return name; + } + case MIOPEN_POINTWISE_ELU_BWD: { + static const std::string name = "OP_POINTWISE:ELU_BWD"; + return name; + } + case MIOPEN_POINTWISE_GELU_BWD: { + static const std::string name = "OP_POINTWISE:GELU_BWD"; + return name; + } + case MIOPEN_POINTWISE_SOFTPLUS_BWD: { + static const std::string name = "OP_POINTWISE:SOFTPLUS_BWD"; + return name; + } + case MIOPEN_POINTWISE_SWISH_BWD: { + static const std::string name = "OP_POINTWISE:SWISH_BWD"; + return name; + } + case MIOPEN_POINTWISE_GELU_APPROX_TANH_BWD: { + static const std::string name = "OP_POINTWISE:APPROX_TANH_BWD"; + return name; + } + case MIOPEN_POINTWISE_CMP_EQ: { + static const std::string name = "OP_POINTWISE:CMP_EQ"; + return name; + } + case MIOPEN_POINTWISE_CMP_NEQ: { + static const std::string name = "OP_POINTWISE:CMP_NEQ"; + return name; + } + case MIOPEN_POINTWISE_CMP_GT: { + static const std::string name = "OP_POINTWISE:CMP_GT"; + return name; + } + case MIOPEN_POINTWISE_CMP_GE: { + static const std::string name = "OP_POINTWISE:CMP_GE"; + return name; + } + case MIOPEN_POINTWISE_CMP_LT: { + static const std::string name = "OP_POINTWISE:CMP_LT"; + return name; + } + case MIOPEN_POINTWISE_CMP_LE: { + static const std::string name = "OP_POINTWISE:CMP_LE"; + return name; + } + case MIOPEN_POINTWISE_LOGICAL_AND: { + static const std::string name = "OP_POINTWISE:LOGICAL_AND"; + return name; + } + case MIOPEN_POINTWISE_LOGICAL_OR: { + static const std::string name = "OP_POINTWISE:LOGICAL_OR"; + return name; + } + case MIOPEN_POINTWISE_LOGICAL_NOT: { + static const std::string name = "OP_POINTWISE:LOGICAL_NOT"; + return name; + } + case MIOPEN_POINTWISE_GEN_INDEX: { + static const std::string name = "OP_POINTWISE:GEN_INDEX"; + return name; + } + case MIOPEN_POINTWISE_BINARY_SELECT: { + static const std::string name = "OP_POINTWISE:BINARY_SELECT"; + return name; + } + case MIOPEN_POINTWISE_RECIPROCAL: { + static const std::string name = "OP_POINTWISE:RECIPROCAL"; + return name; + } + default: MIOPEN_THROW(miopenStatusNotImplemented); + } +} + +std::vector OperationPointwise::getInTensors() const +{ + switch(mPointwise->getMode()) + { + /* 2-inputs operations + * x input + * b input + * y output + */ + case MIOPEN_POINTWISE_ADD: + case MIOPEN_POINTWISE_ADD_SQUARE: + case MIOPEN_POINTWISE_DIV: + case MIOPEN_POINTWISE_MAX: + case MIOPEN_POINTWISE_MIN: + case MIOPEN_POINTWISE_MOD: + case MIOPEN_POINTWISE_MUL: + case MIOPEN_POINTWISE_POW: + case MIOPEN_POINTWISE_SUB: + case MIOPEN_POINTWISE_CMP_EQ: + case MIOPEN_POINTWISE_CMP_NEQ: + case MIOPEN_POINTWISE_CMP_GT: + case MIOPEN_POINTWISE_CMP_GE: + case MIOPEN_POINTWISE_CMP_LT: + case MIOPEN_POINTWISE_CMP_LE: + case MIOPEN_POINTWISE_LOGICAL_AND: + case MIOPEN_POINTWISE_LOGICAL_OR: return {mX, mB}; + + /* Single input operations + * x input + * y output + */ + case MIOPEN_POINTWISE_ABS: + case MIOPEN_POINTWISE_CEIL: + case MIOPEN_POINTWISE_COS: + case MIOPEN_POINTWISE_EXP: + case MIOPEN_POINTWISE_FLOOR: + case MIOPEN_POINTWISE_LOG: + case MIOPEN_POINTWISE_NEG: + case MIOPEN_POINTWISE_RSQRT: + case MIOPEN_POINTWISE_SIN: + case MIOPEN_POINTWISE_SQRT: + case MIOPEN_POINTWISE_TAN: + case MIOPEN_POINTWISE_IDENTITY: + case MIOPEN_POINTWISE_RELU_FWD: + case MIOPEN_POINTWISE_TANH_FWD: + case MIOPEN_POINTWISE_SIGMOID_FWD: + case MIOPEN_POINTWISE_ELU_FWD: + case MIOPEN_POINTWISE_GELU_FWD: + case MIOPEN_POINTWISE_SOFTPLUS_FWD: + case MIOPEN_POINTWISE_SWISH_FWD: + case MIOPEN_POINTWISE_GELU_APPROX_TANH_FWD: + case MIOPEN_POINTWISE_LOGICAL_NOT: + case MIOPEN_POINTWISE_RECIPROCAL: return {mX}; + + /* 3-inputs operations + * x input + * b input + * t input + * y output + */ + case MIOPEN_POINTWISE_BINARY_SELECT: return {mX, mB, mT}; + + /* 2-inputs backward operations + * y input + * dy input + * dx output + */ + case MIOPEN_POINTWISE_RELU_BWD: + case MIOPEN_POINTWISE_TANH_BWD: + case MIOPEN_POINTWISE_SIGMOID_BWD: + case MIOPEN_POINTWISE_ELU_BWD: + case MIOPEN_POINTWISE_GELU_BWD: + case MIOPEN_POINTWISE_SOFTPLUS_BWD: + case MIOPEN_POINTWISE_SWISH_BWD: + case MIOPEN_POINTWISE_GELU_APPROX_TANH_BWD: return {mY, mDy}; + + /* TODO: Implement the remaining cases + */ + case MIOPEN_POINTWISE_ERF: + case MIOPEN_POINTWISE_GEN_INDEX: return {}; + + default: MIOPEN_THROW(miopenStatusNotImplemented); + } +} + +std::vector OperationPointwise::getOutTensors() const +{ + switch(mPointwise->getMode()) + { + /* 2-inputs operations + * x input + * b input + * y output + */ + case MIOPEN_POINTWISE_ADD: + case MIOPEN_POINTWISE_ADD_SQUARE: + case MIOPEN_POINTWISE_DIV: + case MIOPEN_POINTWISE_MAX: + case MIOPEN_POINTWISE_MIN: + case MIOPEN_POINTWISE_MOD: + case MIOPEN_POINTWISE_MUL: + case MIOPEN_POINTWISE_POW: + case MIOPEN_POINTWISE_SUB: + case MIOPEN_POINTWISE_CMP_EQ: + case MIOPEN_POINTWISE_CMP_NEQ: + case MIOPEN_POINTWISE_CMP_GT: + case MIOPEN_POINTWISE_CMP_GE: + case MIOPEN_POINTWISE_CMP_LT: + case MIOPEN_POINTWISE_CMP_LE: + case MIOPEN_POINTWISE_LOGICAL_AND: + case MIOPEN_POINTWISE_LOGICAL_OR: + /* Single input operations + * x input + * y output + */ + case MIOPEN_POINTWISE_ABS: + case MIOPEN_POINTWISE_CEIL: + case MIOPEN_POINTWISE_COS: + case MIOPEN_POINTWISE_EXP: + case MIOPEN_POINTWISE_FLOOR: + case MIOPEN_POINTWISE_LOG: + case MIOPEN_POINTWISE_NEG: + case MIOPEN_POINTWISE_RSQRT: + case MIOPEN_POINTWISE_SIN: + case MIOPEN_POINTWISE_SQRT: + case MIOPEN_POINTWISE_TAN: + case MIOPEN_POINTWISE_IDENTITY: + case MIOPEN_POINTWISE_RELU_FWD: + case MIOPEN_POINTWISE_TANH_FWD: + case MIOPEN_POINTWISE_SIGMOID_FWD: + case MIOPEN_POINTWISE_ELU_FWD: + case MIOPEN_POINTWISE_GELU_FWD: + case MIOPEN_POINTWISE_SOFTPLUS_FWD: + case MIOPEN_POINTWISE_SWISH_FWD: + case MIOPEN_POINTWISE_GELU_APPROX_TANH_FWD: + case MIOPEN_POINTWISE_LOGICAL_NOT: + case MIOPEN_POINTWISE_RECIPROCAL: + /* 3-inputs operations + * x input + * b input + * t input + * y output + */ + case MIOPEN_POINTWISE_BINARY_SELECT: return {mY}; + + /* 2-inputs backward operations + * y input + * dy input + * dx output + */ + case MIOPEN_POINTWISE_RELU_BWD: + case MIOPEN_POINTWISE_TANH_BWD: + case MIOPEN_POINTWISE_SIGMOID_BWD: + case MIOPEN_POINTWISE_ELU_BWD: + case MIOPEN_POINTWISE_GELU_BWD: + case MIOPEN_POINTWISE_SOFTPLUS_BWD: + case MIOPEN_POINTWISE_SWISH_BWD: + case MIOPEN_POINTWISE_GELU_APPROX_TANH_BWD: return {mDx}; + + /* TODO: Implement the remaining cases + */ + case MIOPEN_POINTWISE_ERF: + case MIOPEN_POINTWISE_GEN_INDEX: return {}; + + default: MIOPEN_THROW(miopenStatusNotImplemented); + } +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setPointwise(Pointwise* pointwise) +{ + mOperationPointwise.mPointwise = checkPtr(pointwise); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setX(Tensor* x) +{ + mOperationPointwise.mX = checkPtr(x); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setB(Tensor* b) +{ + mOperationPointwise.mB = checkPtr(b); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setY(Tensor* y) +{ + mOperationPointwise.mY = checkPtr(y); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setT(Tensor* t) +{ + mOperationPointwise.mT = checkPtr(t); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setDx(Tensor* dX) +{ + mOperationPointwise.mDx = checkPtr(dX); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setDy(Tensor* dY) +{ + mOperationPointwise.mDy = checkPtr(dY); + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setAlpha1(OperationPointwise::Alpha alpha1) +{ + mOperationPointwise.mAlpha1 = alpha1; + return *this; +} + +OperationPointwiseBuilder& OperationPointwiseBuilder::setAlpha2(OperationPointwise::Alpha alpha2) +{ + mOperationPointwise.mAlpha2 = alpha2; + mAlpha2Set = true; + return *this; +} + +namespace { + +template +bool checkDimsWithPossibleBroadcasting(Range1 input1, Range2 input2, Range3 output) +{ + auto input1it = input1.cbegin(); + auto input1Last = input1.cend(); + auto input2it = input2.cbegin(); + auto input2Last = input2.cend(); + auto outputit = output.cbegin(); + auto outputLast = output.cend(); + + bool OK = true; + + for(; OK && input1it != input1Last && input2it != input2Last && outputit != outputLast; + ++input1it, ++input2it, ++outputit) + { + OK = (*input1it == *input2it && *input1it == *outputit) || + (*input1it == 1 && *input2it > 1 && *input2it == *outputit) || + (*input2it == 1 && *input1it > 1 && *input1it == *outputit); + } + OK = OK && input1it == input1Last && input2it == input2Last && outputit == outputLast; + + return OK; +} + +} // namespace + +OperationPointwise OperationPointwiseBuilder::build() +{ + if(mOperationPointwise.mPointwise == nullptr) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + switch(mOperationPointwise.mPointwise->getMode()) + { + /* 2-inputs operations + * x input + * b input + * y output + */ + case MIOPEN_POINTWISE_ADD: + case MIOPEN_POINTWISE_ADD_SQUARE: + case MIOPEN_POINTWISE_DIV: + case MIOPEN_POINTWISE_MAX: + case MIOPEN_POINTWISE_MIN: + case MIOPEN_POINTWISE_MOD: + case MIOPEN_POINTWISE_MUL: + case MIOPEN_POINTWISE_POW: + case MIOPEN_POINTWISE_SUB: + case MIOPEN_POINTWISE_CMP_EQ: + case MIOPEN_POINTWISE_CMP_NEQ: + case MIOPEN_POINTWISE_CMP_GT: + case MIOPEN_POINTWISE_CMP_GE: + case MIOPEN_POINTWISE_CMP_LT: + case MIOPEN_POINTWISE_CMP_LE: + case MIOPEN_POINTWISE_LOGICAL_AND: + case MIOPEN_POINTWISE_LOGICAL_OR: + if(mOperationPointwise.mX == nullptr || mOperationPointwise.mB == nullptr || + mOperationPointwise.mY == nullptr || mOperationPointwise.mT != nullptr || + mOperationPointwise.mDx != nullptr || mOperationPointwise.mDy != nullptr || + !checkDimsWithPossibleBroadcasting(mOperationPointwise.mX->getDimensions(), + mOperationPointwise.mB->getDimensions(), + mOperationPointwise.mY->getDimensions())) + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + /* Single input operations + * x input + * y output + */ + case MIOPEN_POINTWISE_ABS: + case MIOPEN_POINTWISE_CEIL: + case MIOPEN_POINTWISE_COS: + case MIOPEN_POINTWISE_EXP: + case MIOPEN_POINTWISE_FLOOR: + case MIOPEN_POINTWISE_LOG: + case MIOPEN_POINTWISE_NEG: + case MIOPEN_POINTWISE_RSQRT: + case MIOPEN_POINTWISE_SIN: + case MIOPEN_POINTWISE_SQRT: + case MIOPEN_POINTWISE_TAN: + case MIOPEN_POINTWISE_IDENTITY: + case MIOPEN_POINTWISE_RELU_FWD: + case MIOPEN_POINTWISE_TANH_FWD: + case MIOPEN_POINTWISE_SIGMOID_FWD: + case MIOPEN_POINTWISE_ELU_FWD: + case MIOPEN_POINTWISE_GELU_FWD: + case MIOPEN_POINTWISE_SOFTPLUS_FWD: + case MIOPEN_POINTWISE_SWISH_FWD: + case MIOPEN_POINTWISE_GELU_APPROX_TANH_FWD: + case MIOPEN_POINTWISE_LOGICAL_NOT: + case MIOPEN_POINTWISE_RECIPROCAL: + if(mOperationPointwise.mX == nullptr || mOperationPointwise.mY == nullptr || + mOperationPointwise.mB != nullptr || mOperationPointwise.mT != nullptr || + mOperationPointwise.mDx != nullptr || mOperationPointwise.mDy != nullptr || mAlpha2Set || + !std::equal(mOperationPointwise.mX->getDimensions().cbegin(), + mOperationPointwise.mX->getDimensions().cend(), + mOperationPointwise.mY->getDimensions().cbegin(), + mOperationPointwise.mY->getDimensions().cend())) + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + /* 3-inputs operations + * x input + * b input + * t input + * y output + */ + case MIOPEN_POINTWISE_BINARY_SELECT: + if(mOperationPointwise.mX == nullptr || mOperationPointwise.mB == nullptr || + mOperationPointwise.mT == nullptr || mOperationPointwise.mY == nullptr || + mOperationPointwise.mDx != nullptr || mOperationPointwise.mDy != nullptr || + !std::equal(mOperationPointwise.mX->getDimensions().cbegin(), + mOperationPointwise.mX->getDimensions().cend(), + mOperationPointwise.mB->getDimensions().cbegin(), + mOperationPointwise.mB->getDimensions().cend()) || + !std::equal(mOperationPointwise.mX->getDimensions().cbegin(), + mOperationPointwise.mX->getDimensions().cend(), + mOperationPointwise.mT->getDimensions().cbegin(), + mOperationPointwise.mT->getDimensions().cend()) || + !std::equal(mOperationPointwise.mX->getDimensions().cbegin(), + mOperationPointwise.mX->getDimensions().cend(), + mOperationPointwise.mY->getDimensions().cbegin(), + mOperationPointwise.mY->getDimensions().cend())) + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + /* 2-inputs backward operations + * y input + * dy input + * dx output + */ + case MIOPEN_POINTWISE_RELU_BWD: + case MIOPEN_POINTWISE_TANH_BWD: + case MIOPEN_POINTWISE_SIGMOID_BWD: + case MIOPEN_POINTWISE_ELU_BWD: + case MIOPEN_POINTWISE_GELU_BWD: + case MIOPEN_POINTWISE_SOFTPLUS_BWD: + case MIOPEN_POINTWISE_SWISH_BWD: + case MIOPEN_POINTWISE_GELU_APPROX_TANH_BWD: + if(mOperationPointwise.mY == nullptr || mOperationPointwise.mDy == nullptr || + mOperationPointwise.mDx == nullptr || mOperationPointwise.mX != nullptr || + mOperationPointwise.mB != nullptr || mOperationPointwise.mT != nullptr || + !checkDimsWithPossibleBroadcasting(mOperationPointwise.mY->getDimensions(), + mOperationPointwise.mDy->getDimensions(), + mOperationPointwise.mDx->getDimensions())) + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + /* TODO: Implement the remaining cases + */ + case MIOPEN_POINTWISE_ERF: + case MIOPEN_POINTWISE_GEN_INDEX: MIOPEN_THROW(miopenStatusNotImplemented); + + default: MIOPEN_THROW(miopenStatusNotImplemented); + } + + return mOperationPointwise; +} + +void BackendOperationPointwiseDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using TensorSetter = OperationPointwiseBuilder& (OperationPointwiseBuilder::*)(Tensor * tensor); + + auto callTensorSetter = [=](TensorSetter setter, miopenBackendDescriptor_t& outApiDescriptor) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendTensorDescriptor& tensorDescriptor = + dynamic_cast(backendDescriptor); + (mBuilder.*setter)(tensorDescriptor.getTensor()); + outApiDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + using FloatSetter = + OperationPointwiseBuilder& (OperationPointwiseBuilder::*)(OperationPointwise::Alpha alpha); + + auto callFloatSetter = [=](FloatSetter setter) { + if(attributeType == MIOPEN_TYPE_FLOAT && elementCount == 1) + { + (mBuilder.*setter)(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_POINTWISE_PW_DESCRIPTOR: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendPointwiseDescriptor& pointwiseDescriptor = + dynamic_cast(backendDescriptor); + mBuilder.setPointwise(pointwiseDescriptor.getPointwise()); + mPointwiseDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_XDESC: + callTensorSetter(&OperationPointwiseBuilder::setX, mXDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_BDESC: + callTensorSetter(&OperationPointwiseBuilder::setB, mBDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_YDESC: + callTensorSetter(&OperationPointwiseBuilder::setY, mYDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_TDESC: + callTensorSetter(&OperationPointwiseBuilder::setT, mTDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_DXDESC: + callTensorSetter(&OperationPointwiseBuilder::setDx, mDxDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_DYDESC: + callTensorSetter(&OperationPointwiseBuilder::setDy, mDyDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_ALPHA1: + callFloatSetter(&OperationPointwiseBuilder::setAlpha1); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_ALPHA2: + callFloatSetter(&OperationPointwiseBuilder::setAlpha2); + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationPointwiseDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mOperationPointwise = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationPointwiseDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + auto storeDescriptor = [=](miopenBackendDescriptor_t descriptor) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = descriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + auto storeFloat = [=](OperationPointwise::Alpha alpha) { + if(attributeType == MIOPEN_TYPE_FLOAT && requestedElementCount == 1 && alpha.index() == 0) + { + *elementCount = 1; + *static_cast(arrayOfElements) = std::get(alpha); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_POINTWISE_PW_DESCRIPTOR: + storeDescriptor(mPointwiseDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_XDESC: storeDescriptor(mXDescriptor); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_BDESC: storeDescriptor(mBDescriptor); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_YDESC: storeDescriptor(mYDescriptor); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_TDESC: storeDescriptor(mTDescriptor); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_DXDESC: storeDescriptor(mDxDescriptor); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_DYDESC: storeDescriptor(mDyDescriptor); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_ALPHA1: storeFloat(mOperationPointwise.getAlpha1()); break; + + case MIOPEN_ATTR_OPERATION_POINTWISE_ALPHA2: storeFloat(mOperationPointwise.getAlpha2()); break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OpNode* BackendOperationPointwiseDescriptor::getOperation() { return &mOperationPointwise; } + +} // namespace graphapi + +} // namespace miopen diff --git a/src/graphapi/reduction.cpp b/src/graphapi/reduction.cpp new file mode 100644 index 0000000000..69565ad151 --- /dev/null +++ b/src/graphapi/reduction.cpp @@ -0,0 +1,343 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace graphapi { + +Reduction ReductionBuilder::build() +{ + if(mReductionOperatorSet && mCompTypeSet) + { + return mReduction; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendReductionDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_REDUCTION_OPERATOR: + if(attributeType == MIOPEN_TYPE_REDUCTION_OPERATOR_TYPE && elementCount == 1) + { + mBuilder.setReductionOperator(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_REDUCTION_COMP_TYPE: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && elementCount == 1) + { + mBuilder.setCompType(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendReductionDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mReduction = mBuilder.build(); + mFinalized = true; +} + +void BackendReductionDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_REDUCTION_OPERATOR: + if(attributeType == MIOPEN_TYPE_REDUCTION_OPERATOR_TYPE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = + mReduction.getReductionOperator(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_REDUCTION_COMP_TYPE: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mReduction.getCompType(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +const std::string& OperationReduction::signName() const +{ + switch(mReduction->getReductionOperator()) + { + case MIOPEN_REDUCE_TENSOR_ADD: { + static const std::string name = "OP_REDUCTION:ADD"; + return name; + } + case MIOPEN_REDUCE_TENSOR_MUL: { + static const std::string name = "OP_REDUCTION:MUK"; + return name; + } + case MIOPEN_REDUCE_TENSOR_MIN: { + static const std::string name = "OP_REDUCTION:MIN"; + return name; + } + case MIOPEN_REDUCE_TENSOR_MAX: { + static const std::string name = "OP_REDUCTION:MAX"; + return name; + } + case MIOPEN_REDUCE_TENSOR_AMAX: { + static const std::string name = "OP_REDUCTION:AMAX"; + return name; + } + case MIOPEN_REDUCE_TENSOR_AVG: { + static const std::string name = "OP_REDUCTION:AVG"; + return name; + } + case MIOPEN_REDUCE_TENSOR_NORM1: { + static const std::string name = "OP_REDUCTION:NORM1"; + return name; + } + case MIOPEN_REDUCE_TENSOR_NORM2: { + static const std::string name = "OP_REDUCTION:NORM2"; + return name; + } + default: MIOPEN_THROW(miopenStatusNotImplemented); + } +} + +std::vector OperationReduction::getInTensors() const { return {mX}; } + +std::vector OperationReduction::getOutTensors() const { return {mY}; } + +OperationReductionBuilder& OperationReductionBuilder::setReduction(Reduction* reduction) +{ + mOperationReduction.mReduction = checkPtr(reduction); + return *this; +} + +OperationReductionBuilder& OperationReductionBuilder::setX(Tensor* x) +{ + mOperationReduction.mX = checkPtr(x); + return *this; +} + +OperationReductionBuilder& OperationReductionBuilder::setY(Tensor* y) +{ + mOperationReduction.mY = checkPtr(y); + return *this; +} + +OperationReduction OperationReductionBuilder::build() +{ + if(mOperationReduction.mReduction != nullptr && mOperationReduction.mX != nullptr && + mOperationReduction.mY != nullptr && + std::equal(mOperationReduction.mX->getDimensions().cbegin(), + mOperationReduction.mX->getDimensions().cend(), + mOperationReduction.mY->getDimensions().cbegin(), + mOperationReduction.mY->getDimensions().cend(), + [](auto inputDim, auto outputDim) { + return outputDim == 1 || outputDim == inputDim || outputDim % inputDim == 0; + })) + { + return mOperationReduction; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationReductionDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using Setter = OperationReductionBuilder& (OperationReductionBuilder::*)(Tensor * tensor); + + auto callSetter = [=](Setter setter, miopenBackendDescriptor_t& outDescriptor) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendTensorDescriptor& tensorDescriptor = + dynamic_cast(backendDescriptor); + (mBuilder.*setter)(tensorDescriptor.getTensor()); + outDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_REDUCTION_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendReductionDescriptor& reductionDescriptor = + dynamic_cast(backendDescriptor); + mBuilder.setReduction(reductionDescriptor.getReduction()); + mReductionDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_REDUCTION_XDESC: + callSetter(&OperationReductionBuilder::setX, mXDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_REDUCTION_YDESC: + callSetter(&OperationReductionBuilder::setY, mYDescriptor); + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationReductionDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mOperationReduction = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationReductionDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + auto storeDescriptor = [=](miopenBackendDescriptor_t descriptor) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = descriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_REDUCTION_DESC: storeDescriptor(mReductionDescriptor); break; + + case MIOPEN_ATTR_OPERATION_REDUCTION_XDESC: storeDescriptor(mXDescriptor); break; + + case MIOPEN_ATTR_OPERATION_REDUCTION_YDESC: storeDescriptor(mYDescriptor); break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OpNode* BackendOperationReductionDescriptor::getOperation() { return &mOperationReduction; } + +} // namespace graphapi + +} // namespace miopen diff --git a/src/graphapi/rng.cpp b/src/graphapi/rng.cpp new file mode 100644 index 0000000000..f5eeb66701 --- /dev/null +++ b/src/graphapi/rng.cpp @@ -0,0 +1,431 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace graphapi { + +RngBuilder& RngBuilder::setNormalStdev(double normalStdev) +{ + if(normalStdev > 0) + { + mRng.mNormalStdev = normalStdev; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + return *this; +} + +RngBuilder& RngBuilder::setBernoulliProb(double bernoulliProb) +{ + if(bernoulliProb >= 0.0 && bernoulliProb <= 1.0) + { + mRng.mBernoulliProb = bernoulliProb; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + return *this; +} + +Rng RngBuilder::build() const +{ + if((mRng.mDistribution == MIOPEN_RNG_DISTRIBUTION_NORMAL && mRng.mNormalStdev > 0.0) || + (mRng.mDistribution == MIOPEN_RNG_DISTRIBUTION_UNIFORM && + mRng.mUniformMin <= mRng.mUniformMax) || + (mRng.mDistribution == MIOPEN_RNG_DISTRIBUTION_BERNOULLI && mRng.mBernoulliProb >= 0.0 && + mRng.mBernoulliProb <= 1.0)) + { + return mRng; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendRngDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using Setter = RngBuilder& (RngBuilder::*)(double); + + auto callSetter = [=](Setter setter) { + if(attributeType == MIOPEN_TYPE_DOUBLE && elementCount == 1) + { + (mBuilder.*setter)(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_RNG_DISTRIBUTION: + if(attributeType == MIOPEN_TYPE_RNG_DISTRIBUTION && elementCount == 1) + { + mBuilder.setDistribution(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_RNG_NORMAL_DIST_MEAN: callSetter(&RngBuilder::setNormalMean); break; + + case MIOPEN_ATTR_RNG_NORMAL_DIST_STANDARD_DEVIATION: + callSetter(&RngBuilder::setNormalStdev); + break; + + case MIOPEN_ATTR_RNG_UNIFORM_DIST_MINIMUM: callSetter(&RngBuilder::setUniformMin); break; + + case MIOPEN_ATTR_RNG_UNIFORM_DIST_MAXIMUM: callSetter(&RngBuilder::setUniformMax); break; + + case MIOPEN_ATTR_RNG_BERNOULLI_DIST_PROBABILITY: + callSetter(&RngBuilder::setBernoulliProb); + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendRngDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mRng = mBuilder.build(); + mFinalized = true; +} + +void BackendRngDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using Getter = double (Rng::*)() const; + + auto callGetter = [=](Getter getter) { + if(attributeType == MIOPEN_TYPE_DOUBLE && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = (mRng.*getter)(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_RNG_DISTRIBUTION: + if(attributeType == MIOPEN_TYPE_RNG_DISTRIBUTION && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mRng.getDistribution(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_RNG_NORMAL_DIST_MEAN: callGetter(&Rng::getNormalMean); break; + + case MIOPEN_ATTR_RNG_NORMAL_DIST_STANDARD_DEVIATION: callGetter(&Rng::getNormalStdev); break; + + case MIOPEN_ATTR_RNG_UNIFORM_DIST_MINIMUM: callGetter(&Rng::getUniformMin); break; + + case MIOPEN_ATTR_RNG_UNIFORM_DIST_MAXIMUM: callGetter(&Rng::getUniformMax); break; + + case MIOPEN_ATTR_RNG_BERNOULLI_DIST_PROBABILITY: callGetter(&Rng::getBernoulliProb); break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +const std::string& OperationRng::signName() const +{ + static const std::string name = "OP_RNG"; + return name; +} + +std::vector OperationRng::getInTensors() const +{ + if(mSeed.index() == 0) + { + return {mOffset}; + } + else + { + return {std::get(mSeed), mOffset}; + } +} + +std::vector OperationRng::getOutTensors() const { return {mOutput}; } + +OperationRngBuilder& OperationRngBuilder::setRng(Rng* rng) +{ + mOperationRng.mRng = checkPtr(rng); + return *this; +} + +OperationRngBuilder& OperationRngBuilder::setOutput(Tensor* output) +{ + mOperationRng.mOutput = checkPtr(output); + return *this; +} + +OperationRngBuilder& OperationRngBuilder::setSeed(int64_t seed) noexcept +{ + mOperationRng.mSeed = seed; + return *this; +} + +OperationRngBuilder& OperationRngBuilder::setSeed(Tensor* seed) +{ + bool valid = seed != nullptr; + + valid = valid && miopen::all_of(seed->getDimensions(), [](auto v) { return v == 1; }) && + miopen::all_of(seed->getStrides(), [](auto v) { return v == 1; }); + + if(valid) + { + mOperationRng.mSeed = seed; + return *this; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +OperationRngBuilder& OperationRngBuilder::setOffset(Tensor* offset) +{ + bool valid = offset != nullptr; + + valid = valid && miopen::all_of(offset->getDimensions(), [](auto v) { return v == 1; }) && + miopen::all_of(offset->getStrides(), [](auto v) { return v == 1; }); + + if(valid) + { + mOperationRng.mOffset = offset; + return *this; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } +} + +OperationRng OperationRngBuilder::build() +{ + if(mOperationRng.mRng == nullptr || mOperationRng.mOutput == nullptr || + mOperationRng.mOffset == nullptr) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + return mOperationRng; +} + +void BackendOperationRngDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + using TensorSetter = OperationRngBuilder& (OperationRngBuilder::*)(Tensor*); + + auto callTensorSetter = [=](TensorSetter setter, miopenBackendDescriptor_t& outApiDescriptor) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendTensorDescriptor& tensorDescriptor = + dynamic_cast(backendDescriptor); + (mBuilder.*setter)(tensorDescriptor.getTensor()); + outApiDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_RNG_DESC: + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && elementCount == 1) + { + miopenBackendDescriptor_t apiDescriptor = + deref(static_cast(arrayOfElements)); + BackendDescriptor& backendDescriptor = deref(apiDescriptor); + + if(!backendDescriptor.isFinalized()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + BackendRngDescriptor& rngDescriptor = + dynamic_cast(backendDescriptor); + mBuilder.setRng(rngDescriptor.getRng()); + mRngDescriptor = apiDescriptor; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_RNG_YDESC: + callTensorSetter(&OperationRngBuilder::setOutput, mOutputDescriptor); + break; + + case MIOPEN_ATTR_OPERATION_RNG_SEED: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount == 1) + { + mBuilder.setSeed(*static_cast(arrayOfElements)); + } + else + { + callTensorSetter(&OperationRngBuilder::setSeed, mSeedDescriptor); + } + break; + + case MIOPEN_ATTR_OPERATION_RNG_OFFSET_DESC: + callTensorSetter(&OperationRngBuilder::setOffset, mOffsetDescriptor); + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendOperationRngDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mOperationRng = mBuilder.build(); + mFinalized = true; +} + +void BackendOperationRngDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + auto retrieveDescriptor = [=](miopenBackendDescriptor_t source) { + if(attributeType == MIOPEN_TYPE_BACKEND_DESCRIPTOR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = source; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + }; + + switch(attributeName) + { + case MIOPEN_ATTR_OPERATION_RNG_DESC: retrieveDescriptor(mRngDescriptor); break; + + case MIOPEN_ATTR_OPERATION_RNG_YDESC: retrieveDescriptor(mOutputDescriptor); break; + + case MIOPEN_ATTR_OPERATION_RNG_SEED: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = std::get(mOperationRng.getSeed()); + } + else if(mOperationRng.getSeed().index() == 1) + { + retrieveDescriptor(mSeedDescriptor); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_OPERATION_RNG_OFFSET_DESC: retrieveDescriptor(mOffsetDescriptor); break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +OpNode* BackendOperationRngDescriptor::getOperation() { return &mOperationRng; } + +} // namespace graphapi + +} // namespace miopen diff --git a/src/graphapi/tensor.cpp b/src/graphapi/tensor.cpp new file mode 100644 index 0000000000..55d34731f5 --- /dev/null +++ b/src/graphapi/tensor.cpp @@ -0,0 +1,314 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace graphapi { + +TensorBuilder& TensorBuilder::setDataType(miopenDataType_t dataType) & +{ + mDataType = dataType; + mDataTypeSet = true; + return *this; +} + +TensorBuilder& TensorBuilder::setDim(const std::vector& dimensions) & +{ + if(dimensions.empty() || miopen::any_of(dimensions, [](int64_t val) { return val <= 0; })) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mDimensions = dimensions; + mDimensionsSet = true; + return *this; +} + +TensorBuilder& TensorBuilder::setDim(std::vector&& dimensions) & +{ + if(dimensions.empty() || miopen::any_of(dimensions, [](int64_t val) { return val <= 0; })) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mDimensions = std::move(dimensions); + mDimensionsSet = true; + return *this; +} + +TensorBuilder& TensorBuilder::setStride(const std::vector& strides) & +{ + if(strides.empty() || miopen::any_of(strides, [](int64_t val) { return val <= 0; })) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mStrides = strides; + mStridesSet = true; + return *this; +} + +TensorBuilder& TensorBuilder::setStride(std::vector&& strides) & +{ + if(strides.empty() || miopen::any_of(strides, [](int64_t val) { return val <= 0; })) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mStrides = std::move(strides); + mStridesSet = true; + return *this; +} + +TensorBuilder& TensorBuilder::setId(int64_t id) & +{ + mId = id; + mUniqueIdSet = true; + return *this; +} + +TensorBuilder& TensorBuilder::setVirtual(bool isVirtual) & +{ + mVirtual = isVirtual; + return *this; +} + +Tensor TensorBuilder::build() const& +{ + if(!mUniqueIdSet || !mDataTypeSet || !mDimensionsSet || !mStridesSet || + mDimensions.size() != mStrides.size()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + return {mDataType, mDimensions, mStrides, mId, mVirtual}; +} + +Tensor TensorBuilder::build() && +{ + if(!mUniqueIdSet || !mDataTypeSet || !mDimensionsSet || !mStridesSet || + mDimensions.size() != mStrides.size()) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + return {mDataType, std::move(mDimensions), std::move(mStrides), mId, mVirtual}; +} + +BackendTensorDescriptor::~BackendTensorDescriptor() = default; + +void BackendTensorDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_TENSOR_UNIQUE_ID: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount == 1) + { + mBuilder.setId(*static_cast(arrayOfElements)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_DATA_TYPE: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && elementCount == 1) + { + mBuilder.setDataType(*static_cast(arrayOfElements)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_DIMENSIONS: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount > 0) + { + mBuilder.setDim( + std::vector(static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_STRIDES: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount > 0) + { + mBuilder.setStride( + std::vector(static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_IS_VIRTUAL: + if(attributeType == MIOPEN_TYPE_BOOLEAN && elementCount == 1) + { + mBuilder.setVirtual(*static_cast(arrayOfElements)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_BYTE_ALIGNMENT: + case MIOPEN_ATTR_TENSOR_VECTOR_COUNT: + case MIOPEN_ATTR_TENSOR_VECTORIZED_DIMENSION: + case MIOPEN_ATTR_TENSOR_RAGGED_OFFSET_DESC: MIOPEN_THROW(miopenStatusNotImplemented); + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendTensorDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mDescriptor = std::move(mBuilder).build(); + mFinalized = true; +} + +void BackendTensorDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_TENSOR_UNIQUE_ID: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount == 1) + { + *static_cast(arrayOfElements) = mDescriptor.getId(); + *elementCount = 1; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_DATA_TYPE: + if(attributeType == MIOPEN_TYPE_DATA_TYPE && requestedElementCount == 1) + { + *static_cast(arrayOfElements) = mDescriptor.getDataType(); + *elementCount = 1; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_DIMENSIONS: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount >= 0) + { + const auto& dimensions = mDescriptor.getDimensions(); + *elementCount = dimensions.size(); + std::copy_n(dimensions.begin(), + // WORKAROUND: building on Windows is failing due to conflicting definitions + // of std::min() between the MSVC standard library and HIP Clang wrappers. + *elementCount < requestedElementCount ? *elementCount + : requestedElementCount, + static_cast(arrayOfElements)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_STRIDES: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount >= 0) + { + const auto& strides = mDescriptor.getStrides(); + *elementCount = strides.size(); + std::copy_n(strides.begin(), + // WORKAROUND: building on Windows is failing due to conflicting definitions + // of std::min() between the MSVC standard library and HIP Clang wrappers. + *elementCount < requestedElementCount ? *elementCount + : requestedElementCount, + static_cast(arrayOfElements)); + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_IS_VIRTUAL: + if(attributeType == MIOPEN_TYPE_BOOLEAN && requestedElementCount == 1) + { + *static_cast(arrayOfElements) = mDescriptor.isVirtual(); + *elementCount = 1; + return; + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + + case MIOPEN_ATTR_TENSOR_BYTE_ALIGNMENT: + case MIOPEN_ATTR_TENSOR_VECTOR_COUNT: + case MIOPEN_ATTR_TENSOR_VECTORIZED_DIMENSION: + case MIOPEN_ATTR_TENSOR_RAGGED_OFFSET_DESC: MIOPEN_THROW(miopenStatusNotImplemented); + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +} // namespace graphapi + +} // namespace miopen diff --git a/src/graphapi/variant_pack.cpp b/src/graphapi/variant_pack.cpp new file mode 100644 index 0000000000..9034ab1b88 --- /dev/null +++ b/src/graphapi/variant_pack.cpp @@ -0,0 +1,161 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace graphapi { + +void BackendVariantPackDescriptor::setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_VARIANT_PACK_UNIQUE_IDS: + if(attributeType == MIOPEN_TYPE_INT64 && elementCount >= 0) + { + mBuilder.setTensorIds( + std::vector(static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_VARIANT_PACK_DATA_POINTERS: + if(attributeType == MIOPEN_TYPE_VOID_PTR && elementCount >= 0) + { + // Don't use braced-list syntax here, it creates a 2-element vector + mBuilder.setDataPointers( + std::vector(static_cast(arrayOfElements), + static_cast(arrayOfElements) + elementCount)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_VARIANT_PACK_INTERMEDIATES: MIOPEN_THROW(miopenStatusNotImplemented); + + case MIOPEN_ATTR_VARIANT_PACK_WORKSPACE: + if(attributeType == MIOPEN_TYPE_VOID_PTR && elementCount == 1) + { + mBuilder.setWorkspace(*static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +void BackendVariantPackDescriptor::finalize() +{ + if(mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + mVariantPack = std::move(mBuilder).build(); + mFinalized = true; +} + +void BackendVariantPackDescriptor::getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) +{ + if(!mFinalized) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + + switch(attributeName) + { + case MIOPEN_ATTR_VARIANT_PACK_UNIQUE_IDS: + if(attributeType == MIOPEN_TYPE_INT64 && requestedElementCount >= 0) + { + *elementCount = mVariantPack.mTensorIds.size(); + std::copy_n(mVariantPack.mTensorIds.cbegin(), + std::min(*elementCount, requestedElementCount), + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_VARIANT_PACK_DATA_POINTERS: + if(attributeType == MIOPEN_TYPE_VOID_PTR && requestedElementCount >= 0) + { + *elementCount = mVariantPack.mDataPointers.size(); + std::copy_n(mVariantPack.mDataPointers.cbegin(), + std::min(*elementCount, requestedElementCount), + static_cast(arrayOfElements)); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + case MIOPEN_ATTR_VARIANT_PACK_INTERMEDIATES: MIOPEN_THROW(miopenStatusNotImplemented); + + case MIOPEN_ATTR_VARIANT_PACK_WORKSPACE: + if(attributeType == MIOPEN_TYPE_VOID_PTR && requestedElementCount == 1) + { + *elementCount = 1; + *static_cast(arrayOfElements) = mVariantPack.getWorkspace(); + } + else + { + MIOPEN_THROW(miopenStatusBadParm); + } + break; + + default: MIOPEN_THROW(miopenStatusBadParm); + } +} + +} // namespace graphapi + +} // namespace miopen diff --git a/src/hip/handlehip.cpp b/src/hip/handlehip.cpp index 27f947d17a..d569ec8ae3 100644 --- a/src/hip/handlehip.cpp +++ b/src/hip/handlehip.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -493,8 +494,17 @@ Program Handle::LoadProgram(const std::string& program_name, std::string orig_params = params; // make a copy for target ID fallback - if(!miopen::EndsWith(program_name, ".mlir")) - params = params + " -mcpu=" + this->GetTargetProperties().Name(); + if(miopen::EndsWith(program_name, ".mlir")) + { // no -mcpu + } + else if(miopen::EndsWith(program_name, ".s")) + { + params += " -mcpu=" + LcOptionTargetStrings{this->GetTargetProperties()}.targetId; + } + else + { + params += " -mcpu=" + this->GetTargetProperties().Name(); + } auto hsaco = miopen::LoadBinary( this->GetTargetProperties(), this->GetMaxComputeUnits(), program_name, params); @@ -522,9 +532,8 @@ Program Handle::LoadProgram(const std::string& program_name, // Save to cache #if MIOPEN_ENABLE_SQLITE_KERN_CACHE - miopen::SaveBinary(p.IsCodeObjectInMemory() - ? p.GetCodeObjectBlob() - : miopen::LoadFile(p.GetCodeObjectPathname().string()), + miopen::SaveBinary(p.IsCodeObjectInMemory() ? p.GetCodeObjectBlob() + : miopen::LoadFile(p.GetCodeObjectPathname()), this->GetTargetProperties(), this->GetMaxComputeUnits(), program_name, diff --git a/src/hip/hip_build_utils.cpp b/src/hip/hip_build_utils.cpp index 3e6b70bba9..f11c4f5351 100644 --- a/src/hip/hip_build_utils.cpp +++ b/src/hip/hip_build_utils.cpp @@ -49,7 +49,7 @@ MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_HIP_DUMP) // This flag Paths are expected to be deprecated/modified // in upcoming MAJOR Releases. #define MIOPEN_CLANG_REL_PATH "llvm/bin/clang" -#define MIOPEN_OCL_REL_PATH "bin/clang-ocl" +#define MIOPEN_OCL_REL_PATH "bin/clang" #define MIOPEN_CPPCLANG_REL_PATH "llvm/bin/clang++" #define MIOPEN_OFFLOADBUNDLER_REL_PATH "llvm/bin/clang-offload-bundler" @@ -114,7 +114,6 @@ static fs::path HipBuildImpl(boost::optional& tmp_dir, const TargetProperties& target, const bool testing_mode) { -#ifdef __linux__ // Write out the include files // Let's assume includes are overkill for feature tests & optimize'em out. if(!testing_mode) @@ -132,11 +131,8 @@ static fs::path HipBuildImpl(boost::optional& tmp_dir, src += "\nint main() {}\n"; WriteFile(src, tmp_dir->path / filename); - // cppcheck-suppress unreadVariable const LcOptionTargetStrings lots(target); - auto env = std::string(""); - if(params.find("-std=") == std::string::npos) params += " --std=c++17"; @@ -174,20 +170,20 @@ static fs::path HipBuildImpl(boost::optional& tmp_dir, #endif // hip version - params += - std::string(" -DHIP_PACKAGE_VERSION_FLAT=") + std::to_string(HIP_PACKAGE_VERSION_FLAT); - - params += " "; - auto bin_file = tmp_dir->path / (filename + ".o"); + params += " -DHIP_PACKAGE_VERSION_FLAT=" + std::string{HIP_PACKAGE_VERSION_FLAT} + " "; + auto bin_file = make_object_file_name(tmp_dir.get() / filename); // compile { - const std::string redirector = testing_mode ? " 1>/dev/null 2>&1" : ""; - const std::string cmd = env + std::string(" ") + MIOPEN_HIP_COMPILER; - const std::string args = params + filename + " -o " + bin_file.string() + redirector; - tmp_dir->Execute(cmd, args); + std::string args = params + filename + " -o " + bin_file; +#ifndef _WIN32 + // Windows uses WIN32 API to execute a subprocess. None command shell is spawned. + if(testing_mode) + args += " 1>/dev/null 2>&1"; +#endif + tmp_dir->Execute(MIOPEN_HIP_COMPILER, args); if(!fs::exists(bin_file)) - MIOPEN_THROW("Failed cmd: '" + cmd + "', args: '" + args + '\''); + MIOPEN_THROW("Failed cmd: '" MIOPEN_HIP_COMPILER "', args: '" + args + '\''); } #if defined(MIOPEN_OFFLOADBUNDLER_BIN) && !MIOPEN_BACKEND_HIP @@ -195,9 +191,8 @@ static fs::path HipBuildImpl(boost::optional& tmp_dir, tmp_dir->Execute(MIOPEN_OFFLOADBUNDLER_BIN, "--type=o " "--targets=hipv4-amdgcn-amd-amdhsa-" + - (std::string{'-'} + lots.device + lots.xnack) + - " --inputs=" + bin_file.string() + " --outputs=" + bin_file.string() + - ".hsaco --unbundle"); + (std::string{'-'} + lots.device + lots.xnack) + " --inputs=" + bin_file + + " --outputs=" + bin_file + ".hsaco --unbundle"); auto hsaco = std::find_if(fs::directory_iterator{tmp_dir->path}, {}, [](auto entry) { return (entry.path().extension() == ".hsaco"); @@ -205,15 +200,11 @@ static fs::path HipBuildImpl(boost::optional& tmp_dir, if(hsaco == fs::directory_iterator{}) { - MIOPEN_LOG_E("failed to find *.hsaco in " << hsaco->path().string()); + MIOPEN_LOG_E("failed to find *.hsaco in " << hsaco->path()); } return hsaco->path(); -#endif - return bin_file; #else - (void)filename; - (void)params; - MIOPEN_THROW("HIP kernels are only supported in Linux"); + return bin_file; #endif } @@ -228,12 +219,4 @@ fs::path HipBuild(boost::optional& tmp_dir, return HipBuildImpl(tmp_dir, filename, src, params, target, false); } -void bin_file_to_str(const fs::path& file, std::string& buf) -{ - std::ifstream bin_file_ptr(file.string().c_str(), std::ios::binary); - std::ostringstream bin_file_strm; - bin_file_strm << bin_file_ptr.rdbuf(); - buf = bin_file_strm.str(); -} - } // namespace miopen diff --git a/src/hipoc/hipoc_program.cpp b/src/hipoc/hipoc_program.cpp index ce3398e8bb..3373c66b2a 100644 --- a/src/hipoc/hipoc_program.cpp +++ b/src/hipoc/hipoc_program.cpp @@ -151,7 +151,7 @@ static hipModulePtr CreateModule(const fs::path& hsaco_file) auto status = hipModuleLoad(&raw_m, hsaco_file.string().c_str()); hipModulePtr m{raw_m}; if(status != hipSuccess) - MIOPEN_THROW_HIP_STATUS(status, "Failed creating module from file " + hsaco_file.string()); + MIOPEN_THROW_HIP_STATUS(status, "Failed creating module from file " + hsaco_file); return m; } @@ -172,7 +172,7 @@ HIPOCProgramImpl::HIPOCProgramImpl(const std::string& program_name, const fs::pa module = CreateModule(hsaco_file); } -HIPOCProgramImpl::HIPOCProgramImpl(const std::string& program_name, const std::string& blob) +HIPOCProgramImpl::HIPOCProgramImpl(const std::string& program_name, const std::vector& blob) : program(program_name) ///, module(CreateModuleInMem(blob)) { const auto& arch = miopen::GetStringEnv(ENV(MIOPEN_DEVICE_ARCH)); @@ -209,7 +209,7 @@ void HIPOCProgramImpl::BuildCodeObjectInFile(std::string& params, { dir.emplace(filename); - hsaco_file = dir->path / (filename + ".o"); + hsaco_file = make_object_file_name(dir.get() / filename); if(miopen::EndsWith(filename, ".so")) { @@ -242,11 +242,11 @@ void HIPOCProgramImpl::BuildCodeObjectInFile(std::string& params, params += " -cl-kernel-arg-info -cl-denorms-are-zero"; params += " -cl-std=CL2.0 -mllvm -amdgpu-early-inline-all"; params += " -mllvm -amdgpu-internalize-symbols "; - params += " " + filename + " -o " + hsaco_file.string(); + params += " " + filename + " -o " + hsaco_file; dir->Execute(HIP_OC_COMPILER, params); } if(!fs::exists(hsaco_file)) - MIOPEN_THROW("Cant find file: " + hsaco_file.string()); + MIOPEN_THROW("Cant find file: " + hsaco_file); } #else // MIOPEN_USE_COMGR @@ -341,7 +341,7 @@ HIPOCProgram::HIPOCProgram(const std::string& program_name, const fs::path& hsac { } -HIPOCProgram::HIPOCProgram(const std::string& program_name, const std::string& hsaco) +HIPOCProgram::HIPOCProgram(const std::string& program_name, const std::vector& hsaco) : impl(std::make_shared(program_name, hsaco)) { } @@ -360,10 +360,7 @@ fs::path HIPOCProgram::GetCodeObjectPathname() const } } -std::string HIPOCProgram::GetCodeObjectBlob() const -{ - return {impl->binary.data(), impl->binary.size()}; -} +std::vector HIPOCProgram::GetCodeObjectBlob() const { return impl->binary; } void HIPOCProgram::FreeCodeObjectFileStorage() { diff --git a/src/include/miopen/batch_norm.hpp b/src/include/miopen/batch_norm.hpp index 82625f7bf0..0582a7d335 100644 --- a/src/include/miopen/batch_norm.hpp +++ b/src/include/miopen/batch_norm.hpp @@ -29,6 +29,7 @@ #include #include +#include #include #define MIO_BN_CPP_PROF 0 diff --git a/src/include/miopen/binary_cache.hpp b/src/include/miopen/binary_cache.hpp index 50367bff02..c5704c44eb 100644 --- a/src/include/miopen/binary_cache.hpp +++ b/src/include/miopen/binary_cache.hpp @@ -2,7 +2,7 @@ * * MIT License * - * Copyright (c) 2017 Advanced Micro Devices, Inc. + * Copyright (c) 2024 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 @@ -51,12 +51,12 @@ void SaveBinary(const fs::path& binary_path, const std::string& name, const std::string& args); #else -std::string LoadBinary(const TargetProperties& target, - std::size_t num_cu, - const std::string& name, - const std::string& args); +std::vector LoadBinary(const TargetProperties& target, + std::size_t num_cu, + const std::string& name, + const std::string& args); -void SaveBinary(const std::string& hsaco, +void SaveBinary(const std::vector& hsaco, const TargetProperties& target, std::size_t num_cu, const std::string& name, diff --git a/src/include/miopen/bz2.hpp b/src/include/miopen/bz2.hpp index 921ccfa1bf..707c7032b9 100644 --- a/src/include/miopen/bz2.hpp +++ b/src/include/miopen/bz2.hpp @@ -26,14 +26,13 @@ #ifndef GUARD_MIOPEN_BZ2_HPP_ #define GUARD_MIOPEN_BZ2_HPP_ -#include +#include #include -#include namespace miopen { void check_bz2_error(int e, const std::string& name); -std::string compress(std::string s, bool* compressed = nullptr); -std::string decompress(std::string s, unsigned int size); +std::vector compress(const std::vector& v, bool* compressed = nullptr); +std::vector decompress(const std::vector& v, unsigned int size); } // namespace miopen diff --git a/src/include/miopen/conv/asm_implicit_gemm.hpp b/src/include/miopen/conv/asm_implicit_gemm.hpp index d57028086f..9490fa246a 100644 --- a/src/include/miopen/conv/asm_implicit_gemm.hpp +++ b/src/include/miopen/conv/asm_implicit_gemm.hpp @@ -43,6 +43,10 @@ /// https://github.com/ROCm/MIOpen/issues/2624 #define WORKAROUND_ISSUE_2624 1 +/// W/A for issue 2624: asm igemm wrw computation error with stride=2, padding=2, filter=3, h=w=1 +/// https://github.com/ROCm/MIOpen/issues/2867 +#define WORKAROUND_ISSUE_2867 1 + namespace miopen { namespace solver { diff --git a/src/include/miopen/conv/heuristics/ai_heuristics.hpp b/src/include/miopen/conv/heuristics/ai_heuristics.hpp index 9b3cdf193a..3a231e7571 100644 --- a/src/include/miopen/conv/heuristics/ai_heuristics.hpp +++ b/src/include/miopen/conv/heuristics/ai_heuristics.hpp @@ -64,6 +64,8 @@ struct Metadata const std::unordered_map solver_map; const std::vector features_mean; const std::vector features_std; + const std::vector test_features_mean; + const std::vector test_features_std; Metadata(const std::string& arch); size_t EncodeDirection(miopen::conv::Direction dir) const; size_t EncodePrecision(miopenDataType_t data_type) const; @@ -80,13 +82,14 @@ std::vector PredictSolver(const conv::ProblemDescription& problem, namespace tuning { struct Metadata { - std::size_t num_tuning_params; + std::unordered_map num_tuning_params; std::unordered_map tuning_decodings; Metadata(const std::string& arch, const std::string& solver); }; bool ModelSetParams(const std::string& arch, const std::string& solver, + conv::Direction direction, const std::vector& features, bool transform_features, std::function validator); diff --git a/src/include/miopen/conv/problem_description.hpp b/src/include/miopen/conv/problem_description.hpp index 7812828a73..8bba1ba3c5 100644 --- a/src/include/miopen/conv/problem_description.hpp +++ b/src/include/miopen/conv/problem_description.hpp @@ -189,24 +189,8 @@ struct ProblemDescription : ProblemDescriptionBase std::size_t GetInStrideH() const { return GetH5(GetSpatialDims(), in.GetStrides()); } std::size_t GetInStrideW() const { return GetW5(GetSpatialDims(), in.GetStrides()); } std::string GetInLayout() const { return in_layout; } - std::string ComputeInLayout() const - { - if(GetSpatialDims() == 2) - { - return in.GetLayout(in.GetLayout_str()); - } - else - { - return in.GetLayout("NCDHW"); - } - } std::size_t GetInElementSize() const { return GetTypeSize(GetInDataType()); } - - std::size_t GetInSize() const - { - return GetInBatchSize() * GetInChannels() * GetInDepth() * GetInHeight() * GetInWidth() * - GetInElementSize(); - } + std::size_t GetInSize() const { return in.GetNumBytes(); } // Out getters miopenDataType_t GetOutDataType() const { return out.GetType(); } @@ -222,24 +206,8 @@ struct ProblemDescription : ProblemDescriptionBase std::size_t GetOutStrideH() const { return GetH5(GetSpatialDims(), out.GetStrides()); } std::size_t GetOutStrideW() const { return GetW5(GetSpatialDims(), out.GetStrides()); } std::string GetOutLayout() const { return out_layout; } - std::string ComputeOutLayout() const - { - if(GetSpatialDims() == 2) - { - return out.GetLayout(out.GetLayout_str()); - } - else - { - return out.GetLayout("NCDHW"); - } - } std::size_t GetOutElementSize() const { return GetTypeSize(GetOutDataType()); } - - std::size_t GetOutSize() const - { - return GetOutBatchSize() * GetOutChannels() * GetOutDepth() * GetOutHeight() * - GetOutWidth() * GetOutElementSize(); - } + std::size_t GetOutSize() const { return out.GetNumBytes(); } // Weights getters miopenDataType_t GetWeightsDataType() const { return weights.GetType(); } @@ -266,24 +234,8 @@ struct ProblemDescription : ProblemDescriptionBase // std::size_t GetWeightsStrideW() const { return GetW5(GetSpatialDims(), weights.GetStrides()); // } std::string GetWeightsLayout() const { return weights_layout; } - std::string ComputeWeightsLayout() const - { - if(GetSpatialDims() == 2) - { - return weights.GetLayout(weights.GetLayout_str()); - } - else - { - return weights.GetLayout("NCDHW"); - } - } std::size_t GetWeightsElementSize() const { return GetTypeSize(GetWeightsDataType()); } - - std::size_t GetWeightsSize() const - { - return GetInChannels() * GetOutChannels() * GetWeightsDepth() * GetWeightsHeight() * - GetWeightsWidth() * GetWeightsElementSize(); - } + std::size_t GetWeightsSize() const { return weights.GetNumBytes(); } const TensorDescriptor& GetIn() const { return in; } const TensorDescriptor& GetWeights() const { return weights; } @@ -396,6 +348,12 @@ struct ProblemDescription : ProblemDescriptionBase return in.AllDimsFitIntoInt() && weights.AllDimsFitIntoInt() && out.AllDimsFitIntoInt(); } + bool AllTensorsLengthsFitIntoInt() const + { + return in.AllLengthsFitIntoInt() && weights.AllLengthsFitIntoInt() && + out.AllLengthsFitIntoInt(); + } + void HeuristicUpdateLayouts(); void MakeNetworkConfig(std::string& conf_key) const; @@ -420,6 +378,7 @@ struct ProblemDescription : ProblemDescriptionBase #if MIOPEN_ENABLE_SQLITE static std::string table_name() { return "config"; } +#endif template static void Visit(Self&& self, std::function f) @@ -457,11 +416,54 @@ struct ProblemDescription : ProblemDescriptionBase f(data_type, "data_type"); f(self.GetDirectionStr(), "direction"); } -#endif + + template + static void VisitAll(Self&& self, const Visitor& f) + { + Visit(std::forward(self), [&](int64_t value, std::string name) { f(value, name); }); + Visit(std::forward(self), + [&](std::string value, std::string name) { f(value, name); }); + } void SetupFloats(ExecutionContext& ctx) const; private: + std::string ComputeInLayout() const + { + if(GetSpatialDims() == 2) + { + return in.GetLayout(in.GetLayout_str()); + } + else + { + return in.GetLayout("NCDHW"); + } + } + + std::string ComputeOutLayout() const + { + if(GetSpatialDims() == 2) + { + return out.GetLayout(out.GetLayout_str()); + } + else + { + return out.GetLayout("NCDHW"); + } + } + + std::string ComputeWeightsLayout() const + { + if(GetSpatialDims() == 2) + { + return weights.GetLayout(weights.GetLayout_str()); + } + else + { + return weights.GetLayout("NCDHW"); + } + } + TensorDescriptor in; TensorDescriptor weights; TensorDescriptor out; diff --git a/src/include/miopen/db.hpp b/src/include/miopen/db.hpp index 67195c8884..b89f1ca16e 100644 --- a/src/include/miopen/db.hpp +++ b/src/include/miopen/db.hpp @@ -52,7 +52,7 @@ constexpr bool DisableUserDbFileIO = MIOPEN_DISABLE_USERDB; class PlainTextDb { public: - PlainTextDb(const std::string& filename_, bool is_system = false); + PlainTextDb(DbKinds db_kind_, const std::string& filename_, bool is_system = false); /// Searches db for provided key and returns found record or none if key not found in database boost::optional FindRecord(const std::string& key); @@ -60,7 +60,7 @@ class PlainTextDb template inline boost::optional FindRecord(const T& problem_config) { - const auto key = DbRecord::Serialize(problem_config); + const auto key = DbRecord::SerializeKey(db_kind, problem_config); return FindRecord(key); } @@ -90,7 +90,7 @@ class PlainTextDb template inline bool Remove(const T& problem_config, const std::string& id) { - const auto key = DbRecord::Serialize(problem_config); + const auto key = DbRecord::SerializeKey(db_kind, problem_config); return Remove(key, id); } @@ -99,7 +99,7 @@ class PlainTextDb template inline bool RemoveRecord(const T& problem_config) { - const auto key = DbRecord::Serialize(problem_config); + const auto key = DbRecord::SerializeKey(db_kind, problem_config); return RemoveRecord(key); } @@ -112,7 +112,7 @@ class PlainTextDb inline boost::optional Update(const T& problem_config, const std::string& id, const V& values) { - DbRecord record(problem_config); + DbRecord record(db_kind, problem_config); record.SetValues(id, values); const auto ok = UpdateRecord(record); if(ok) @@ -138,6 +138,8 @@ class PlainTextDb } protected: + const DbKinds db_kind; + LockFile& GetLockFile() { return lock_file; } const std::string& GetFileName() const { return filename; } bool IsWarningIfUnreadable() const { return warning_if_unreadable; } @@ -161,33 +163,34 @@ class PlainTextDb } }; -template -TRet GetDbInstance(rank<1>, const std::string& path, bool is_system) +template +auto GetDbInstance(rank<1>, DbKinds db_kind, const std::string& path, bool is_system) + -> decltype(TDb::GetCached({}, {}, {})) { - return TDb::GetCached(path, is_system); + return TDb::GetCached(db_kind, path, is_system); }; template -TDb GetDbInstance(rank<0>, const std::string& path, bool is_system) +auto GetDbInstance(rank<0>, DbKinds db_kind, const std::string& path, bool is_system) { - return {path, is_system}; + return TDb{db_kind, path, is_system}; }; -template (rank<1>{}, {}, {}))> -TRet GetDbInstance(const std::string& path, bool is_system = true) +template +decltype(auto) GetDbInstance(DbKinds db_kind, const std::string& path, bool is_system = true) { - return GetDbInstance(rank<1>{}, path, is_system); + return GetDbInstance(rank<1>{}, db_kind, path, is_system); } template class MultiFileDb { public: - MultiFileDb(const std::string& installed_path, const std::string& user_path) - : _installed(GetDbInstance(installed_path, true)) + MultiFileDb(DbKinds db_kind, const std::string& installed_path, const std::string& user_path) + : _installed(GetDbInstance(db_kind, installed_path, true)) #if !MIOPEN_DISABLE_USERDB , - _user(GetDbInstance(user_path, false)) + _user(GetDbInstance(db_kind, user_path, false)) #endif { } @@ -256,27 +259,29 @@ class MultiFileDb } private: - template - static TRet GetDbInstance(rank<1>, const std::string& path, bool warn_if_unreadable) + template + static TRet + GetDbInstance(rank<1>, DbKinds db_kind, const std::string& path, bool warn_if_unreadable) { - return TDb::GetCached(path, warn_if_unreadable); + return TDb::GetCached(db_kind, path, warn_if_unreadable); }; template - static TDb GetDbInstance(rank<0>, const std::string& path, bool warn_if_unreadable) + static TDb + GetDbInstance(rank<0>, DbKinds db_kind, const std::string& path, bool warn_if_unreadable) { - return {path, warn_if_unreadable}; + return {db_kind, path, warn_if_unreadable}; }; - template (rank<1>{}, {}, {}))> - static TRet GetDbInstance(const std::string& path, bool warn_if_unreadable) + template (rank<1>{}, {}, {}, {}))> + static TRet GetDbInstance(DbKinds db_kind, const std::string& path, bool warn_if_unreadable) { - return GetDbInstance(rank<1>{}, path, warn_if_unreadable); + return GetDbInstance(rank<1>{}, db_kind, path, warn_if_unreadable); } - decltype(MultiFileDb::GetDbInstance("", true)) _installed; + decltype(MultiFileDb::GetDbInstance(DbKinds::FindDb, "", true)) _installed; #if !MIOPEN_DISABLE_USERDB - decltype(MultiFileDb::GetDbInstance("", false)) _user; + decltype(MultiFileDb::GetDbInstance(DbKinds::FindDb, "", false)) _user; #endif }; diff --git a/src/include/miopen/db_record.hpp b/src/include/miopen/db_record.hpp index af898f9941..d136190f86 100644 --- a/src/include/miopen/db_record.hpp +++ b/src/include/miopen/db_record.hpp @@ -38,6 +38,13 @@ namespace miopen { +enum class DbKinds : std::uint8_t +{ + FindDb, + PerfDb, + KernelDb, +}; + /// db consists of 0 or more records. /// Each record is an ASCII text line. /// Record format: @@ -170,6 +177,27 @@ class DbRecord return ss.str(); } + template + static // 'static' is for calling from ctor + std::string + SerializeKey(DbKinds db_kind, const T& data) + { + std::ostringstream ss; + if(db_kind == DbKinds::FindDb) + { + data.Serialize(ss); + } + else + { + T::VisitAll(data, [&](auto&& value, auto&&) { + if(ss.tellp() > 0) + ss << "x"; + ss << value; + }); + } + return ss.str(); + } + bool ParseContents(std::istream& contents); void WriteContents(std::ostream& stream) const; void WriteIdsAndValues(std::ostream& stream) const; @@ -189,10 +217,14 @@ class DbRecord /// T shall provide a db KEY by means of the "void Serialize(std::ostream&) const" member /// function. template - DbRecord(const T& problem_config_) : DbRecord(Serialize(problem_config_)) + DbRecord(DbKinds db_kind, const T& problem_config_) + : DbRecord(SerializeKey(db_kind, problem_config_)) { } + // used in tests + DbRecord(DbKinds, const std::string& problem_config_) : DbRecord(problem_config_) {} + auto GetSize() const { return map.size(); } const std::string& GetKey() const { return key; } diff --git a/src/include/miopen/errors.hpp b/src/include/miopen/errors.hpp index a82db5a0f4..ad80e84664 100644 --- a/src/include/miopen/errors.hpp +++ b/src/include/miopen/errors.hpp @@ -127,6 +127,19 @@ auto deref(T&& x, [[maybe_unused]] miopenStatus_t err = miopenStatusBadParm) template auto tie_deref(Ts&... xs) MIOPEN_RETURNS(std::tie(miopen::deref(xs)...)); +template +Ptr checkPtr(Ptr ptr, [[maybe_unused]] miopenStatus_t err = miopenStatusBadParm) +{ + if(ptr != nullptr) + { + return ptr; + } + else + { + MIOPEN_THROW(err, "Passing nullptr"); + } +} + } // namespace miopen #endif diff --git a/src/include/miopen/execution_context.hpp b/src/include/miopen/execution_context.hpp index 43172303a4..84a4178488 100644 --- a/src/include/miopen/execution_context.hpp +++ b/src/include/miopen/execution_context.hpp @@ -119,10 +119,10 @@ struct ExecutionContext std::ostringstream filename; // clang-format off filename << GetStream().GetDbBasename(); -#if MIOPEN_ENABLE_SQLITE +#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB const std::string ext = ".db"; #else - const std::string ext = ".cd.pdb.txt"; + const std::string ext = ".db.txt"; #endif filename << ext; // clang-format on @@ -166,7 +166,7 @@ struct ExecutionContext if(abs(cur_count - real_cu_count) < (closest_cu)) { - MIOPEN_LOG_I2("Updating best candidate to: " << filepath.string()); + MIOPEN_LOG_I2("Updating best candidate to: " << filepath); best_path = filepath; closest_cu = abs(cur_count - real_cu_count); } @@ -186,10 +186,10 @@ struct ExecutionContext std::ostringstream filename; // clang-format off filename << GetStream().GetDbBasename(); -#if MIOPEN_ENABLE_SQLITE +#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB const std::string ext = ".db"; #else - const std::string ext = ".cd.pdb.txt"; + const std::string ext = ".db.txt"; #endif filename << ext; // clang-format on @@ -205,7 +205,7 @@ struct ExecutionContext const int real_cu_count = GetStream().GetMaxComputeUnits(); if(fs::exists(pdb_path) && fs::is_directory(pdb_path)) { - MIOPEN_LOG_I2("Iterating over perf db directory " << pdb_path.string()); + MIOPEN_LOG_I2("Iterating over perf db directory " << pdb_path); int closest_cu = std::numeric_limits::max(); fs::path best_path; std::vector contents; @@ -242,7 +242,7 @@ struct ExecutionContext if(abs(cur_count - real_cu_count) < (closest_cu)) { - MIOPEN_LOG_I2("Updating best candidate to: " << filepath.string()); + MIOPEN_LOG_I2("Updating best candidate to: " << filepath); best_path = filepath; closest_cu = abs(cur_count - real_cu_count); } @@ -279,10 +279,10 @@ struct ExecutionContext return ""; std::ostringstream filename; filename << GetStream().GetDbBasename(); -#if MIOPEN_ENABLE_SQLITE +#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB filename << "_" << SQLitePerfDb::MIOPEN_PERFDB_SCHEMA_VER << ".udb"; #else - filename << "." << GetUserDbSuffix() << ".cd.updb.txt"; + filename << "." << GetUserDbSuffix() << ".udb.txt"; #endif return (udb / filename.str()).string(); } diff --git a/src/include/miopen/filesystem.hpp b/src/include/miopen/filesystem.hpp index 59c89051b4..d1e4296fde 100644 --- a/src/include/miopen/filesystem.hpp +++ b/src/include/miopen/filesystem.hpp @@ -27,6 +27,9 @@ #ifndef GUARD_MIOPEN_FILESYSTEM_HPP_ #define GUARD_MIOPEN_FILESYSTEM_HPP_ +#include +#include + #if defined(CPPCHECK) #define MIOPEN_HAS_FILESYSTEM 1 #define MIOPEN_HAS_FILESYSTEM_TS 1 @@ -75,6 +78,16 @@ namespace fs = ::std::experimental::filesystem; } // namespace miopen +inline std::string operator+(const std::string_view s, const miopen::fs::path& path) +{ + return path.string().insert(0, s); +} + +inline std::string operator+(const miopen::fs::path& path, const std::string_view s) +{ + return path.string().append(s); +} + #if MIOPEN_HAS_FILESYSTEM_TS #ifdef __linux__ #include @@ -96,4 +109,42 @@ inline fs::path weakly_canonical(const fs::path& path) { return fs::weakly_canon } // namespace miopen #endif +namespace miopen { + +#ifdef _WIN32 +constexpr std::string_view executable_postfix{".exe"}; +constexpr std::string_view library_prefix{""}; +constexpr std::string_view dynamic_library_postfix{".dll"}; +constexpr std::string_view static_library_postfix{".lib"}; +constexpr std::string_view object_file_postfix{".obj"}; +#else +constexpr std::string_view executable_postfix{""}; +constexpr std::string_view library_prefix{"lib"}; +constexpr std::string_view dynamic_library_postfix{".so"}; +constexpr std::string_view static_library_postfix{".a"}; +constexpr std::string_view object_file_postfix{".o"}; +#endif + +inline fs::path make_executable_name(const fs::path& path) +{ + return path.parent_path() / (path.filename() + executable_postfix); +} + +inline fs::path make_dynamic_library_name(const fs::path& path) +{ + return path.parent_path() / (library_prefix + path.filename() + dynamic_library_postfix); +} + +inline fs::path make_object_file_name(const fs::path& path) +{ + return path.parent_path() / (path.filename() + object_file_postfix); +} + +inline fs::path make_static_library_name(const fs::path& path) +{ + return path.parent_path() / (library_prefix + path.filename() + static_library_postfix); +} + +} // namespace miopen + #endif // GUARD_MIOPEN_FILESYSTEM_HPP_ diff --git a/src/include/miopen/find_db.hpp b/src/include/miopen/find_db.hpp index cf5b5c07f6..2e6418aeab 100644 --- a/src/include/miopen/find_db.hpp +++ b/src/include/miopen/find_db.hpp @@ -97,9 +97,9 @@ class FindDbRecord_t installed_path(debug::testing_find_db_path_override() ? *debug::testing_find_db_path_override() : GetInstalledPath(handle, path_suffix)), - db(boost::make_optional>(debug::testing_find_db_enabled && - !IsEnabled(ENV(MIOPEN_DEBUG_DISABLE_FIND_DB)), - DbTimer{installed_path, path})) + db(boost::make_optional>( + debug::testing_find_db_enabled && !IsEnabled(ENV(MIOPEN_DEBUG_DISABLE_FIND_DB)), + DbTimer{DbKinds::FindDb, installed_path, path})) { if(!db.is_initialized()) return; @@ -116,11 +116,11 @@ class FindDbRecord_t : path(debug::testing_find_db_path_override() ? *debug::testing_find_db_path_override() : GetUserPath(handle, path_suffix)), #if MIOPEN_DISABLE_USERDB - db(boost::optional>{}) + db(boost::optional>{DbKinds::FindDb}) #else db(boost::make_optional>(debug::testing_find_db_enabled && !IsEnabled(ENV(MIOPEN_DEBUG_DISABLE_FIND_DB)), - DbTimer{path, false})) + DbTimer{DbKinds::FindDb, path, false})) #endif { if(!db.is_initialized()) @@ -164,7 +164,7 @@ class FindDbRecord_t MIOPEN_LOG_I("Find-db regenerating."); ret.clear(); record.in_sync = false; - record.content.emplace(problem); + record.content.emplace(DbKinds::FindDb, problem); regenerator(*record.content); record.CopyTo(ret); diff --git a/src/include/miopen/fusion/problem_description.hpp b/src/include/miopen/fusion/problem_description.hpp index cd9c100c04..5a9d7554d0 100644 --- a/src/include/miopen/fusion/problem_description.hpp +++ b/src/include/miopen/fusion/problem_description.hpp @@ -30,6 +30,7 @@ #include #include #include +#include namespace miopen { @@ -76,6 +77,13 @@ struct FusionDescription : ProblemDescriptionBase auto conv_prob = self.GetConvProblem(conv::Direction::Forward); conv::ProblemDescription::Visit(conv_prob, f); } + + template + static void VisitAll(Self&& self, F f) + { + auto conv_prob = self.GetConvProblem(conv::Direction::Forward); + conv::ProblemDescription::VisitAll(conv_prob, f); + } #endif // This and the following method should be moved to the Ops once the return type can be unified diff --git a/src/include/miopen/gemm_v2.hpp b/src/include/miopen/gemm_v2.hpp index 0319f042dc..57882b3cf6 100644 --- a/src/include/miopen/gemm_v2.hpp +++ b/src/include/miopen/gemm_v2.hpp @@ -28,6 +28,7 @@ #include #include +#include #include namespace miopen { diff --git a/src/include/miopen/graphapi/convolution.hpp b/src/include/miopen/graphapi/convolution.hpp new file mode 100644 index 0000000000..51a6eeedbc --- /dev/null +++ b/src/include/miopen/graphapi/convolution.hpp @@ -0,0 +1,660 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include +#include +#include + +#include + +namespace miopen { + +namespace graphapi { + +class Convolution +{ +private: + int64_t mSpatialDims = 0; + std::vector mDilations; + std::vector mFilterStrides; + std::vector mPrePaddings; + std::vector mPostPaddings; + miopenDataType_t mCompType = miopenFloat; + miopenConvolutionMode_t mMode = miopenConvolution; + +public: + Convolution() noexcept = default; + Convolution(const Convolution&) = default; + Convolution(Convolution&&) noexcept = default; + Convolution& operator=(const Convolution&) = default; + Convolution& operator=(Convolution&&) noexcept = default; + Convolution(miopenDataType_t compType, + miopenConvolutionMode_t mode, + size_t spatialDims, + const std::vector& prePaddings, + const std::vector& filterStrides, + const std::vector& dilations, + const std::vector& postPaddings) + : mSpatialDims(spatialDims), + mDilations(dilations), + mFilterStrides(filterStrides), + mPrePaddings(prePaddings), + mPostPaddings(postPaddings), + mCompType(compType), + mMode(mode) + { + } + Convolution(miopenDataType_t compType, + miopenConvolutionMode_t mode, + size_t spatialDims, + std::vector&& prePaddings, + std::vector&& filterStrides, + std::vector&& dilations, + std::vector&& postPaddings) + : mSpatialDims(spatialDims), + mDilations(std::move(dilations)), + mFilterStrides(std::move(filterStrides)), + mPrePaddings(std::move(prePaddings)), + mPostPaddings(std::move(postPaddings)), + mCompType(compType), + mMode(mode) + { + } + + miopenDataType_t getCompType() const noexcept { return mCompType; } + miopenConvolutionMode_t getMode() const noexcept { return mMode; } + int64_t getSpatialDims() const noexcept { return mSpatialDims; } + const std::vector& getDilations() const noexcept { return mDilations; } + const std::vector& getFilterStrides() const noexcept { return mFilterStrides; } + const std::vector& getPrePaddings() const noexcept { return mPrePaddings; } + const std::vector& getPostPaddings() const noexcept { return mPostPaddings; } + +private: + friend class ConvolutionBuilder; +}; + +class ConvolutionBuilder +{ +private: + Convolution mConvolution; + bool mCompTypeSet = false; + bool mModeSet = false; + bool mSpatialDimsSet = false; + bool mDilationsSet = false; + bool mFilterStridesSet = false; + bool mPrePaddingsSet = false; + bool mPostPaddingsSet = false; + +public: + ConvolutionBuilder& setCompType(miopenDataType_t compType) & noexcept; + ConvolutionBuilder& setMode(miopenConvolutionMode_t mode) & noexcept; + ConvolutionBuilder& setSpatialDims(int64_t spatialDims) & noexcept; + ConvolutionBuilder& setDilations(const std::vector& dilations) &; + ConvolutionBuilder& setDilations(std::vector&& dilations) & noexcept; + ConvolutionBuilder& setFilterStrides(const std::vector& filterStrides) &; + ConvolutionBuilder& setFilterStrides(std::vector&& filterStrides) & noexcept; + ConvolutionBuilder& setPrePaddings(const std::vector& prePaddings) &; + ConvolutionBuilder& setPrePaddings(std::vector&& prePaddings) & noexcept; + ConvolutionBuilder& setPostPaddings(const std::vector& postPaddings) &; + ConvolutionBuilder& setPostPaddings(std::vector&& postPaddings) & noexcept; + + ConvolutionBuilder&& setCompType(miopenDataType_t compType) && noexcept + { + return std::move(setCompType(compType)); + } + ConvolutionBuilder&& setMode(miopenConvolutionMode_t mode) && noexcept + { + return std::move(setMode(mode)); + } + ConvolutionBuilder&& setSpatialDims(int64_t spatialDims) && noexcept + { + return std::move(setSpatialDims(spatialDims)); + } + ConvolutionBuilder&& setDilations(const std::vector& dilations) && + { + return std::move(setDilations(dilations)); + } + ConvolutionBuilder&& setDilations(std::vector&& dilations) && noexcept + { + return std::move(setDilations(std::move(dilations))); + } + ConvolutionBuilder&& setFilterStrides(const std::vector& filterStrides) && + { + return std::move(setFilterStrides(filterStrides)); + } + ConvolutionBuilder&& setFilterStrides(std::vector&& filterStrides) && noexcept + { + return std::move(setFilterStrides(std::move(filterStrides))); + } + ConvolutionBuilder&& setPrePaddings(const std::vector& prePaddings) && + { + return std::move(setPrePaddings(prePaddings)); + } + ConvolutionBuilder&& setPrePaddings(std::vector&& prePaddings) && noexcept + { + return std::move(setPrePaddings(std::move(prePaddings))); + } + ConvolutionBuilder&& setPostPaddings(const std::vector& postPaddings) && + { + return std::move(setPostPaddings(postPaddings)); + } + ConvolutionBuilder&& setPostPaddings(std::vector&& postPaddings) && noexcept + { + return std::move(setPostPaddings(std::move(postPaddings))); + } + + Convolution build() const&; + Convolution build() &&; + +private: + bool validate() const; +}; + +class BackendConvolutionDescriptor : public BackendDescriptor +{ +private: + ConvolutionBuilder mBuilder; + Convolution mConvolution; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const Convolution* getConvolution() const noexcept { return &mConvolution; } + Convolution* getConvolution() noexcept { return &mConvolution; } + +private: + void setCompType(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setMode(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setSpatialDims(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setDilations(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setFilterStrides(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setPrePaddings(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setPostPaddings(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + + void getCompType(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getMode(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getSpatialDims(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getDilations(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getFilterStrides(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getPrePaddings(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getPostPaddings(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); +}; + +class OperationConvolution : public OpNode +{ +private: + Convolution* mConvolution; + Tensor* mX; + Tensor* mY; + Tensor* mW; + double mAlpha; + double mBeta; + +public: + OperationConvolution() = default; + OperationConvolution(Convolution* convolution, + Tensor* x, + Tensor* w, + Tensor* y, + double alpha, + double beta) noexcept + : mConvolution(convolution), mX(x), mY(y), mW(w), mAlpha(alpha), mBeta(beta) + { + } + + Convolution* getConvolution() const noexcept { return mConvolution; } + Tensor* getX() const noexcept { return mX; } + Tensor* getY() const noexcept { return mY; } + Tensor* getW() const noexcept { return mW; } + double getAlpha() const noexcept { return mAlpha; } + double getBeta() const noexcept { return mBeta; } +}; + +class OperationConvolutionForward : public OperationConvolution +{ +public: + OperationConvolutionForward() = default; + OperationConvolutionForward(Convolution* convolution, + Tensor* x, + Tensor* w, + Tensor* y, + double alpha, + double beta) noexcept + : OperationConvolution(convolution, x, w, y, alpha, beta) + { + } + virtual const std::string& signName() const override + { + static const std::string name = "OP_CONVOLUTION_FORWARD"; + return name; + } + virtual std::vector getInTensors() const override { return {getX(), getW()}; } + virtual std::vector getOutTensors() const override { return {getY()}; } +}; + +class OperationConvolutionBuilder +{ +protected: + Convolution* mConvolution = nullptr; + Tensor* mX = nullptr; + Tensor* mY = nullptr; + Tensor* mW = nullptr; + double mAlpha = 1.0; + double mBeta = 0.0; + bool mAlphaSet = false; + bool mBetaSet = false; + +public: + OperationConvolutionBuilder& setConvolution(Convolution* convolution) noexcept + { + mConvolution = convolution; + return *this; + } + OperationConvolutionBuilder& setX(Tensor* x) noexcept + { + mX = x; + return *this; + } + OperationConvolutionBuilder& setY(Tensor* y) noexcept + { + mY = y; + return *this; + } + OperationConvolutionBuilder& setW(Tensor* w) noexcept + { + mW = w; + return *this; + } + OperationConvolutionBuilder& setAlpha(double alpha) noexcept + { + mAlpha = alpha; + mAlphaSet = true; + return *this; + } + OperationConvolutionBuilder& setBeta(double beta) noexcept + { + mBeta = beta; + mBetaSet = true; + return *this; + } + +protected: + OperationConvolutionBuilder() = default; +}; + +class OperationConvolutionForwardBuilder : public OperationConvolutionBuilder +{ +public: + OperationConvolutionForwardBuilder& setConvolution(Convolution* convolution) noexcept + { + OperationConvolutionBuilder::setConvolution(convolution); + return *this; + } + OperationConvolutionForwardBuilder& setX(Tensor* x) noexcept + { + OperationConvolutionBuilder::setX(x); + return *this; + } + OperationConvolutionForwardBuilder& setY(Tensor* y) noexcept + { + OperationConvolutionBuilder::setY(y); + return *this; + } + OperationConvolutionForwardBuilder& setW(Tensor* w) noexcept + { + OperationConvolutionBuilder::setW(w); + return *this; + } + OperationConvolutionForwardBuilder& setAlpha(double alpha) noexcept + { + OperationConvolutionBuilder::setAlpha(alpha); + return *this; + } + OperationConvolutionForwardBuilder& setBeta(double beta) noexcept + { + OperationConvolutionBuilder::setBeta(beta); + return *this; + } + + OperationConvolutionForward build() const + { + if(mConvolution == nullptr || mX == nullptr || mY == nullptr || mW == nullptr || + !mAlphaSet || !mBetaSet) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + return {mConvolution, mX, mW, mY, mAlpha, mBeta}; + } +}; + +class BackendOperationConvolutionDescriptor : public BackendDescriptor +{ +protected: + miopenBackendDescriptor_t mConvolutionDescriptor = nullptr; + miopenBackendDescriptor_t mXDescriptor = nullptr; + miopenBackendDescriptor_t mWDescriptor = nullptr; + miopenBackendDescriptor_t mYDescriptor = nullptr; + + virtual OperationConvolutionBuilder& getBuilder() = 0; + virtual OperationConvolution& getOperationConvolution() = 0; + + void setConvolution(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void + setX(miopenBackendAttributeType_t attributeType, int64_t elementCount, void* arrayOfElements); + void + setW(miopenBackendAttributeType_t attributeType, int64_t elementCount, void* arrayOfElements); + void + setY(miopenBackendAttributeType_t attributeType, int64_t elementCount, void* arrayOfElements); + void setAlpha(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void setBeta(miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements); + void getConvolution(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getX(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getW(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getY(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getAlpha(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); + void getBeta(miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements); +}; + +class BackendOperationConvolutionForwardDescriptor : public BackendOperationConvolutionDescriptor +{ +private: + OperationConvolutionForwardBuilder mBuilder; + OperationConvolutionForward mOperation; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + virtual OpNode* getOperation() override; + +protected: + virtual OperationConvolutionBuilder& getBuilder() override; + virtual OperationConvolution& getOperationConvolution() override; +}; + +class OperationConvolutionBackwardData : public OperationConvolution +{ +public: + OperationConvolutionBackwardData() = default; + OperationConvolutionBackwardData(Convolution* convolution, + Tensor* x, + Tensor* w, + Tensor* y, + double alpha, + double beta) noexcept + : OperationConvolution(convolution, x, w, y, alpha, beta) + { + } + virtual const std::string& signName() const override + { + static const std::string name = "OP_CONVOLUTION_BACKWARD_DATA"; + return name; + } + virtual std::vector getInTensors() const override { return {getW(), getY()}; } + virtual std::vector getOutTensors() const override { return {getX()}; } +}; + +class OperationConvolutionBackwardDataBuilder : public OperationConvolutionBuilder +{ +public: + OperationConvolutionBackwardDataBuilder& setConvolution(Convolution* convolution) noexcept + { + OperationConvolutionBuilder::setConvolution(convolution); + return *this; + } + OperationConvolutionBackwardDataBuilder& setX(Tensor* x) noexcept + { + OperationConvolutionBuilder::setX(x); + return *this; + } + OperationConvolutionBackwardDataBuilder& setY(Tensor* y) noexcept + { + OperationConvolutionBuilder::setY(y); + return *this; + } + OperationConvolutionBackwardDataBuilder& setW(Tensor* w) noexcept + { + OperationConvolutionBuilder::setW(w); + return *this; + } + OperationConvolutionBackwardDataBuilder& setAlpha(double alpha) noexcept + { + OperationConvolutionBuilder::setAlpha(alpha); + return *this; + } + OperationConvolutionBackwardDataBuilder& setBeta(double beta) noexcept + { + OperationConvolutionBuilder::setBeta(beta); + return *this; + } + + OperationConvolutionBackwardData build() const + { + if(mConvolution == nullptr || mX == nullptr || mY == nullptr || mW == nullptr || + !mAlphaSet || !mBetaSet) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + return {mConvolution, mX, mW, mY, mAlpha, mBeta}; + } +}; + +class BackendOperationConvolutionBackwardDataDescriptor + : public BackendOperationConvolutionDescriptor +{ +private: + OperationConvolutionBackwardDataBuilder mBuilder; + OperationConvolutionBackwardData mOperation; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + virtual OpNode* getOperation() override; + +private: + virtual OperationConvolutionBuilder& getBuilder() override; + virtual OperationConvolution& getOperationConvolution() override; +}; + +class OperationConvolutionBackwardFilter : public OperationConvolution +{ +public: + OperationConvolutionBackwardFilter() = default; + OperationConvolutionBackwardFilter(Convolution* convolution, + Tensor* x, + Tensor* w, + Tensor* y, + double alpha, + double beta) noexcept + : OperationConvolution(convolution, x, w, y, alpha, beta) + { + } + virtual const std::string& signName() const override + { + static const std::string name = "OP_CONVOLUTION_BACKWARD_FILTER"; + return name; + } + virtual std::vector getInTensors() const override { return {getX(), getY()}; } + virtual std::vector getOutTensors() const override { return {getW()}; } +}; + +class OperationConvolutionBackwardFilterBuilder : public OperationConvolutionBuilder +{ +public: + OperationConvolutionBackwardFilterBuilder& setConvolution(Convolution* convolution) noexcept + { + OperationConvolutionBuilder::setConvolution(convolution); + return *this; + } + OperationConvolutionBackwardFilterBuilder& setX(Tensor* x) noexcept + { + OperationConvolutionBuilder::setX(x); + return *this; + } + OperationConvolutionBackwardFilterBuilder& setY(Tensor* y) noexcept + { + OperationConvolutionBuilder::setY(y); + return *this; + } + OperationConvolutionBackwardFilterBuilder& setW(Tensor* w) noexcept + { + OperationConvolutionBuilder::setW(w); + return *this; + } + OperationConvolutionBackwardFilterBuilder& setAlpha(double alpha) noexcept + { + OperationConvolutionBuilder::setAlpha(alpha); + return *this; + } + OperationConvolutionBackwardFilterBuilder& setBeta(double beta) noexcept + { + OperationConvolutionBuilder::setBeta(beta); + return *this; + } + + OperationConvolutionBackwardFilter build() const + { + if(mConvolution == nullptr || mX == nullptr || mY == nullptr || mW == nullptr || + !mAlphaSet || !mBetaSet) + { + MIOPEN_THROW(miopenStatusNotInitialized); + } + return {mConvolution, mX, mW, mY, mAlpha, mBeta}; + } +}; + +class BackendOperationConvolutionBackwardFilterDescriptor + : public BackendOperationConvolutionDescriptor +{ +private: + OperationConvolutionBackwardFilterBuilder mBuilder; + OperationConvolutionBackwardFilter mOperation; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + virtual OpNode* getOperation() override; + +private: + virtual OperationConvolutionBuilder& getBuilder() override; + virtual OperationConvolution& getOperationConvolution() override; +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/graphapi/engine.hpp b/src/include/miopen/graphapi/engine.hpp new file mode 100644 index 0000000000..d077b86442 --- /dev/null +++ b/src/include/miopen/graphapi/engine.hpp @@ -0,0 +1,58 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + +#pragma once + +#include +#include + +namespace miopen { + +namespace graphapi { + +class Engine +{ +private: + Solution mSolution; + friend class EngineBuilder; + +public: + Engine() = default; + Engine(const Engine&) = default; + Engine(Engine&&) = default; + Engine& operator=(const Engine&) = default; + Engine& operator=(Engine&&) = default; + + Engine(const Solution& solution) : mSolution(solution) {} + Engine(Solution&& solution) : mSolution(std::move(solution)) {} + + const Solution& getSolution() const { return mSolution; } + Solution& getSolution() { return mSolution; } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/graphapi/graphapi.hpp b/src/include/miopen/graphapi/graphapi.hpp new file mode 100644 index 0000000000..7d0c9d0dd4 --- /dev/null +++ b/src/include/miopen/graphapi/graphapi.hpp @@ -0,0 +1,64 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include +#include + +#include + +namespace miopen { + +namespace graphapi { + +class OpNode; + +class BackendDescriptor : public miopenBackendDescriptor +{ +public: + virtual ~BackendDescriptor(); + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) = 0; + virtual void finalize() = 0; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) = 0; + virtual void execute(miopenHandle_t handle, miopenBackendDescriptor_t variantPack); + virtual OpNode* getOperation(); + + bool isFinalized() const noexcept { return mFinalized; }; + +protected: + bool mFinalized = false; +}; +} // namespace graphapi +} // namespace miopen + +MIOPEN_DEFINE_OBJECT(miopenBackendDescriptor, miopen::graphapi::BackendDescriptor) diff --git a/src/include/miopen/graphapi/matmul.hpp b/src/include/miopen/graphapi/matmul.hpp new file mode 100644 index 0000000000..715d2e0897 --- /dev/null +++ b/src/include/miopen/graphapi/matmul.hpp @@ -0,0 +1,197 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { +namespace graphapi { + +class Matmul +{ +private: + miopenDataType_t mCompType; + +public: + Matmul() = default; + Matmul(miopenDataType_t computeType) : mCompType(computeType) {} + miopenDataType_t getComputeType() { return mCompType; } + +private: + friend class MatmulBuilder; +}; + +class MatmulBuilder +{ + +private: + Matmul mMatmul; + bool mComputeTypeSet = false; + +public: + MatmulBuilder& setComputeType(miopenDataType_t computeType) + { + mMatmul.mCompType = computeType; + mComputeTypeSet = true; + return *this; + } + + Matmul build() const; +}; + +class BackendMatmulDescriptor : public BackendDescriptor +{ +private: + MatmulBuilder mBuilder; + Matmul mMatmul; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const Matmul* getMatmul() const noexcept { return &mMatmul; } + Matmul* getMatmul() noexcept { return &mMatmul; } +}; + +class OperationMatmul : public OpNode +{ +private: + Tensor* mA; + Tensor* mB; + Tensor* mC; + int64_t mBatchCount = 1; + Tensor* mGemmMOverride; + Tensor* mGemmNOverride; + Tensor* mGemmKOverride; + Matmul* mMatmul; + +public: + OperationMatmul(Tensor* A, + Tensor* B, + Tensor* C, + int batchCount, + Tensor* MOverride, + Tensor* NOverride, + Tensor* KOverride, + Matmul* matmul) noexcept + : mA(A), + mB(B), + mC(C), + mBatchCount(batchCount), + mGemmMOverride(MOverride), + mGemmNOverride(NOverride), + mGemmKOverride(KOverride), + mMatmul(matmul) + { + } + + OperationMatmul() = default; + Tensor* getA() const { return mA; } + Tensor* getB() const { return mB; } + Tensor* getC() const { return mC; } + int64_t getBatchCount() const { return mBatchCount; } + Tensor* getMOverride() { return mGemmMOverride; } + Tensor* getNOverride() { return mGemmNOverride; } + Tensor* getKOverride() { return mGemmKOverride; } + Matmul* getMatmul() { return mMatmul; } + virtual std::vector getInTensors() const override { return {getA(), getB()}; } + virtual std::vector getOutTensors() const override { return {getC()}; } + virtual const std::string& signName() const override + { + static const std::string name = "OP_MATMUL"; + return name; + } + +private: + friend class OperationMatmulBuilder; +}; + +class OperationMatmulBuilder +{ +private: + OperationMatmul mOperationMatmul; + bool mASet = false; + bool mBSet = false; + bool mCSet = false; + bool mMatmulSet = false; + +public: + OperationMatmulBuilder& setA(Tensor* A); + + OperationMatmulBuilder& setB(Tensor* B); + + OperationMatmulBuilder& setC(Tensor* C); + + OperationMatmulBuilder& setBatchCount(int64_t count); + + OperationMatmulBuilder& setGemmMOverride(Tensor* overrideTensor); + + OperationMatmulBuilder& setGemmNOverride(Tensor* overrideTensor); + + OperationMatmulBuilder& setGemmKOverride(Tensor* overrideTensor); + + OperationMatmulBuilder& setMatmulDescriptor(Matmul* mMatmul); + + OperationMatmul build(); +}; + +class BackendOperationMatmulDescriptor : public BackendDescriptor +{ +private: + OperationMatmulBuilder mBuilder; + OperationMatmul mMatmul; + miopenBackendDescriptor_t mA = nullptr; + miopenBackendDescriptor_t mB = nullptr; + miopenBackendDescriptor_t mC = nullptr; + miopenBackendDescriptor_t mGemmMOverride = nullptr; + miopenBackendDescriptor_t mGemmNOverride = nullptr; + miopenBackendDescriptor_t mGemmKOverride = nullptr; + miopenBackendDescriptor_t mMatmuDescriptor = nullptr; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; +}; + +} // namespace graphapi +} // namespace miopen diff --git a/src/include/miopen/graphapi/opgraph.hpp b/src/include/miopen/graphapi/opgraph.hpp new file mode 100644 index 0000000000..c2f85ed3fe --- /dev/null +++ b/src/include/miopen/graphapi/opgraph.hpp @@ -0,0 +1,386 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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. + * + *******************************************************************************/ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace miopen { +namespace graphapi { + +namespace internal { +template +bool contains(const C& container, const T& val) noexcept +{ + return std::find(container.cbegin(), container.cend(), val) != container.cend(); +} + +template +bool noRepetitions(const R& r) +{ + auto begin = r.cbegin(); + auto end = r.cend(); + + if(std::distance(begin, end) < threshold) + { + // time = O(n^2) mem = O(1) + bool result = true; + while(result && begin != end) + { + const auto& val = *begin++; + result = std::find(begin, end, val) == end; + } + return result; + } + else + { + // time = O(n) mem = O(n) + std::unordered_set>> seen; + for(; begin != end; ++begin) + { + const auto& val = *begin; + if(seen.find(val) != seen.end()) + { + return false; + } + seen.insert(val); + } + return true; + } +} +} // end namespace internal + +class OpGraphBuilder; +class OpGraph; + +class OpNode +{ +public: + using Edge = std::pair; + virtual ~OpNode(); + + virtual const std::string& signName() const = 0; + +private: + std::vector mInEdges; + std::vector mOutEdges; + + friend class OpGraphBuilder; + friend class OpGraph; + +protected: + virtual std::vector getInTensors() const = 0; + + virtual std::vector getOutTensors() const = 0; + + const auto& getInEdges() const { return mInEdges; } + + const auto& getOutEdges() const { return mOutEdges; } + + bool hasInEdge(const OpNode* src, const Tensor* tens_ptr) const + { + assert(src); + assert(tens_ptr); + Edge e{src, tens_ptr}; + return internal::contains(mInEdges, e); + } + + bool hasOutEdge(const OpNode* dst, const Tensor* tens_ptr) const + { + assert(dst); + assert(tens_ptr); + Edge e{dst, tens_ptr}; + return internal::contains(mOutEdges, e); + } + + void addOutEdge(OpNode* dst, Tensor* tens_ptr) + { + assert(dst); + assert(tens_ptr); + if(!hasOutEdge(dst, tens_ptr)) + { + mOutEdges.emplace_back(dst, tens_ptr); + } + } + + void addInEdge(OpNode* src, Tensor* tens_ptr) + { + assert(src); + assert(tens_ptr); + if(!hasInEdge(src, tens_ptr)) + { + mInEdges.emplace_back(src, tens_ptr); + } + } + + size_t getInDegree() const { return mInEdges.size(); } + size_t getOutDegree() const { return mOutEdges.size(); } +}; + +using OpEdge = OpNode::Edge; + +class SourceOpNode : public OpNode +{ +protected: + std::vector mOutTensors; + friend class OpGraph; + + const std::string& signName() const final + { + static const std::string s = "INTERNAL::SRC"; + return s; + } + + std::vector getInTensors() const final { return {}; } + + std::vector getOutTensors() const final { return mOutTensors; } + + bool hasOutTensor(const Tensor* tensor) const + { + return internal::contains(mOutTensors, tensor); + } + + void addOutTensor(Tensor* tens_ptr) + { + assert(!hasOutTensor(tens_ptr)); + mOutTensors.emplace_back(tens_ptr); + } +}; + +class SinkOpNode : public OpNode +{ +protected: + std::vector mInTensors; + friend class OpGraph; + + const std::string& signName() const final + { + static const std::string s = "INTERNAL::SINK"; + return s; + } + + std::vector getInTensors() const final { return mInTensors; } + + std::vector getOutTensors() const final { return {}; } + + bool hasInTensor(Tensor* tensor) const { return internal::contains(mInTensors, tensor); } + + void addInTensor(Tensor* tens_ptr) + { + assert(!hasInTensor(tens_ptr)); + mInTensors.emplace_back(tens_ptr); + } +}; + +using Path = std::vector; +using VecOfPaths = std::vector; + +class OpGraph +{ + // NOTE: mSrcNode and mSinkNode need to reside on the heap because the graph may move + // to a new memory location after building, while the nodes maintain address + // of SourceOpNode and SinkOpNode in their in and out edge lists + std::unique_ptr mSrcNode = std::make_unique(); + std::unique_ptr mSinkNode = std::make_unique(); + std::vector mNodes{}; + + // Descriptor related members + miopenHandle_t mHandle = nullptr; + std::vector mEngines; + +public: + OpGraph(const OpGraph&) = delete; + OpGraph& operator=(const OpGraph&) = delete; + + OpGraph() = default; + OpGraph(OpGraph&&) = default; + OpGraph& operator=(OpGraph&&) = default; + ~OpGraph() = default; + + bool hasNode(const OpNode* n) const { return internal::contains(mNodes, n); } + + bool hasEdge(const OpNode* src, const Tensor* tens_ptr, const OpNode* dst) const + { + assert(src); + assert(dst); + return src->hasOutEdge(dst, tens_ptr) && dst->hasInEdge(src, tens_ptr); + } + + size_t numNodes() const { return mNodes.size(); } + + size_t numEdges() const + { + size_t ret = 0; + for(OpNode* n : mNodes) + { + ret += n->getOutDegree(); + } + // ignore the edges that lead to mSinkNode + assert(ret >= mSinkNode->getInDegree()); + ret -= mSinkNode->getInDegree(); + + return ret; + } + + std::vector getNodeNames() const + { + std::vector names(mNodes.size()); + for(size_t i = 0; i < mNodes.size(); ++i) + { + names[i] = mNodes[i]->signName(); + } + return names; + } + + std::vector> getInOutDegrees() const + { + std::vector> ret(mNodes.size()); + for(size_t i = 0; i < mNodes.size(); ++i) + { + ret[i] = {mNodes[i]->getInDegree(), mNodes[i]->getOutDegree()}; + } + return ret; + } + + VecOfPaths getAllPaths() const; + + // NOTE: for testing only. May remove in the future + bool hasEdgeFromSource(OpNode* dst, Tensor* tens_ptr) const + { + return hasEdge(mSrcNode.get(), tens_ptr, dst); + } + + // NOTE: for testing only. May remove in the future + bool hasEdgeToSink(OpNode* src, Tensor* tens_ptr) const + { + return hasEdge(src, tens_ptr, mSinkNode.get()); + } + + miopenHandle_t getHandle() const noexcept { return mHandle; } + const std::vector& getEngines() const noexcept { return mEngines; } + +private: + friend class OpGraphBuilder; + + void initNodes(std::vector&& nodes) { mNodes = std::move(nodes); } + + void addEdge(OpNode* src, Tensor* tens_ptr, OpNode* dst) + { + assert(src); + assert(dst); + src->addOutEdge(dst, tens_ptr); + dst->addInEdge(src, tens_ptr); + } + + void addEdgeFromSrc(OpNode* dst, Tensor* tens_ptr) + { + mSrcNode->addOutTensor(tens_ptr); + addEdge(mSrcNode.get(), tens_ptr, dst); + } + + void addEdgeToSink(OpNode* src, Tensor* tens_ptr) + { + mSinkNode->addInTensor(tens_ptr); + addEdge(src, tens_ptr, mSinkNode.get()); + } +}; + +class OpGraphBuilder +{ +private: + std::vector mNodes; + miopenHandle_t mHandle = nullptr; + +public: + void setHandle(miopenHandle_t handle); + + bool hasNode(OpNode* node) const { return internal::contains(mNodes, node); } + + void addNode(OpNode* node) + { + assert(!hasNode(node)); + mNodes.emplace_back(node); + } + + void setNodes(const std::vector& nodes) + { + assert(internal::noRepetitions(nodes)); + mNodes = nodes; + } + + void setNodes(std::vector&& nodes) + { + assert(internal::noRepetitions(nodes)); + mNodes = std::move(nodes); + } + + struct EdgeInfo + { + OpNode* mSrc = nullptr; + std::vector mDests{}; + }; + + // r-value method that consumes *this + OpGraph build() &&; +}; + +bool isIsomorphic(const OpGraph& left, const OpGraph& right); + +std::string pathToStr(const Path& path); + +class BackendOperationGraphDescriptor : public BackendDescriptor +{ +private: + OpGraphBuilder mBuilder; + OpGraph mOpGraph; + std::vector mOps; // to return them in getAttribute + +public: + void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + void finalize() override; + void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const OpGraph* getOperationGraph() const noexcept { return &mOpGraph; } + OpGraph* getOperationGraph() noexcept { return &mOpGraph; } +}; + +} // end namespace graphapi +} // end namespace miopen diff --git a/src/include/miopen/graphapi/pointwise.hpp b/src/include/miopen/graphapi/pointwise.hpp new file mode 100644 index 0000000000..d9e85062c3 --- /dev/null +++ b/src/include/miopen/graphapi/pointwise.hpp @@ -0,0 +1,310 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace miopen { + +namespace graphapi { + +class Pointwise +{ +public: + using FpAttribute = std::variant; + +private: + FpAttribute mReluLowerClip = 0.0f; + FpAttribute mReluUpperClip = std::numeric_limits::max(); + FpAttribute mReluLowerClipSlope = 0.0f; + FpAttribute mEluAlpha = 1.0f; + FpAttribute mSoftPlusBeta = 1.0f; + FpAttribute mSwishBeta = 1.0f; + int64_t mAxis = -1; + miopenPointwiseMode_t mMode; + miopenDataType_t mMathPrecision; + miopenNanPropagation_t mNanPropagation = MIOPEN_NOT_PROPAGATE_NAN; + +public: + Pointwise() noexcept = default; + Pointwise(miopenPointwiseMode_t mode, + miopenDataType_t mathPrecision, + miopenNanPropagation_t nanPropagation = MIOPEN_NOT_PROPAGATE_NAN, + FpAttribute reluLowerClip = 0.0f, + FpAttribute reluUpperClip = std::numeric_limits::max(), + FpAttribute reluLowerClipSlope = 0.0f, + FpAttribute eluAlpha = 1.0f, + FpAttribute softPlusBeta = 1.0f, + FpAttribute swishBeta = 1.0f, + int64_t axis = -1) noexcept + : mReluLowerClip(reluLowerClip), + mReluUpperClip(reluUpperClip), + mReluLowerClipSlope(reluLowerClipSlope), + mEluAlpha(eluAlpha), + mSoftPlusBeta(softPlusBeta), + mSwishBeta(swishBeta), + mAxis(axis), + mMode(mode), + mMathPrecision(mathPrecision), + mNanPropagation(nanPropagation) + { + } + + miopenPointwiseMode_t getMode() const noexcept { return mMode; } + miopenDataType_t getMathPrecision() const noexcept { return mMathPrecision; } + miopenNanPropagation_t getNanPropagation() const noexcept { return mNanPropagation; } + FpAttribute getReluLowerClip() const noexcept { return mReluLowerClip; } + FpAttribute getReluUpperClip() const noexcept { return mReluUpperClip; } + FpAttribute getReluLowerClipSlope() const noexcept { return mReluLowerClipSlope; } + FpAttribute getEluAlpha() const noexcept { return mEluAlpha; } + FpAttribute getSoftPlusBeta() const noexcept { return mSoftPlusBeta; } + FpAttribute getSwishBeta() const noexcept { return mSwishBeta; } + int64_t getAxis() const noexcept { return mAxis; } + +private: + friend class PointwiseBuilder; +}; + +class PointwiseBuilder +{ +private: + Pointwise mPointwise; + bool mModeSet = false; + bool mMathPrecisionSet = false; + +public: + PointwiseBuilder& setMode(miopenPointwiseMode_t mode) noexcept + { + mPointwise.mMode = mode; + mModeSet = true; + return *this; + } + PointwiseBuilder& setMathPrecision(miopenDataType_t mathPrecision) noexcept + { + mPointwise.mMathPrecision = mathPrecision; + mMathPrecisionSet = true; + return *this; + } + PointwiseBuilder& setNanPropagation(miopenNanPropagation_t nanPropagation) noexcept + { + mPointwise.mNanPropagation = nanPropagation; + return *this; + } + PointwiseBuilder& setReluLowerClip(Pointwise::FpAttribute reluLowerClip) noexcept + { + mPointwise.mReluLowerClip = reluLowerClip; + return *this; + } + PointwiseBuilder& setReluUpperClip(Pointwise::FpAttribute reluUpperClip) noexcept + { + mPointwise.mReluUpperClip = reluUpperClip; + return *this; + } + PointwiseBuilder& setReluLowerClipSlope(Pointwise::FpAttribute reluLowerClipSlope) noexcept + { + mPointwise.mReluLowerClipSlope = reluLowerClipSlope; + return *this; + } + PointwiseBuilder& setEluAlpha(Pointwise::FpAttribute eluAlpha) noexcept + { + mPointwise.mEluAlpha = eluAlpha; + return *this; + } + PointwiseBuilder& setSoftPlusBeta(Pointwise::FpAttribute softPlusBeta) noexcept + { + mPointwise.mSoftPlusBeta = softPlusBeta; + return *this; + } + PointwiseBuilder& setSwishBeta(Pointwise::FpAttribute swishBeta) noexcept + { + mPointwise.mSwishBeta = swishBeta; + return *this; + } + PointwiseBuilder& setAxis(int64_t axis) noexcept + { + mPointwise.mAxis = axis; + return *this; + } + + Pointwise build(); +}; + +class BackendPointwiseDescriptor : public BackendDescriptor +{ +private: + PointwiseBuilder mBuilder; + Pointwise mPointwise; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const Pointwise* getPointwise() const { return &mPointwise; } + Pointwise* getPointwise() { return &mPointwise; } +}; + +class OperationPointwise : public OpNode +{ +public: + using Alpha = std::variant; + struct BackwardTag + { + }; + +private: + Pointwise* mPointwise = nullptr; + Tensor* mX = nullptr; + Tensor* mB = nullptr; + Tensor* mY = nullptr; + Tensor* mT = nullptr; + Tensor* mDx = nullptr; + Tensor* mDy = nullptr; + Alpha mAlpha1 = 1.0f; + Alpha mAlpha2 = 1.0f; + + friend class OperationPointwiseBuilder; + +public: + OperationPointwise() noexcept = default; + OperationPointwise(Pointwise* pointwise, Tensor* x, Tensor* y, Alpha alpha1 = 1.0f) + : mPointwise(pointwise), mX(x), mY(y), mAlpha1(alpha1) + { + } + OperationPointwise(Pointwise* pointwise, + Tensor* x, + Tensor* b, + Tensor* y, + Alpha alpha1 = 1.0f, + Alpha alpha2 = 1.0f) noexcept + : mPointwise(pointwise), mX(x), mB(b), mY(y), mAlpha1(alpha1), mAlpha2(alpha2) + { + } + OperationPointwise(Pointwise* pointwise, + Tensor* x, + Tensor* b, + Tensor* y, + Tensor* t, + Alpha alpha1 = 1.0f, + Alpha alpha2 = 1.0f) noexcept + : mPointwise(pointwise), mX(x), mB(b), mY(y), mT(t), mAlpha1(alpha1), mAlpha2(alpha2) + { + } + OperationPointwise(BackwardTag, + Pointwise* pointwise, + Tensor* y, + Tensor* dY, + Tensor* dX, + Alpha alpha1 = 1.0f, + Alpha alpha2 = 1.0f) noexcept + : mPointwise(pointwise), mY(y), mDx(dX), mDy(dY), mAlpha1(alpha1), mAlpha2(alpha2) + { + } + + Pointwise* getPointwise() const noexcept { return mPointwise; } + Tensor* getX() const noexcept { return mX; } + Tensor* getB() const noexcept { return mB; } + Tensor* getY() const noexcept { return mY; } + Tensor* getT() const noexcept { return mT; } + Tensor* getDx() const noexcept { return mDx; } + Tensor* getDy() const noexcept { return mDy; } + Alpha getAlpha1() const noexcept { return mAlpha1; } + Alpha getAlpha2() const noexcept { return mAlpha2; } + + const std::string& signName() const override; + std::vector getInTensors() const override; + std::vector getOutTensors() const override; +}; + +class OperationPointwiseBuilder +{ +private: + OperationPointwise mOperationPointwise; + bool mAlpha2Set = false; + +public: + OperationPointwiseBuilder& setPointwise(Pointwise* pointwise); + OperationPointwiseBuilder& setX(Tensor* x); + OperationPointwiseBuilder& setB(Tensor* b); + OperationPointwiseBuilder& setY(Tensor* y); + OperationPointwiseBuilder& setT(Tensor* t); + OperationPointwiseBuilder& setDx(Tensor* dX); + OperationPointwiseBuilder& setDy(Tensor* dY); + OperationPointwiseBuilder& setAlpha1(OperationPointwise::Alpha alpha1); + OperationPointwiseBuilder& setAlpha2(OperationPointwise::Alpha alpha2); + + OperationPointwise build(); +}; + +class BackendOperationPointwiseDescriptor : public BackendDescriptor +{ +private: + OperationPointwiseBuilder mBuilder; + OperationPointwise mOperationPointwise; + + miopenBackendDescriptor_t mPointwiseDescriptor = nullptr; + miopenBackendDescriptor_t mXDescriptor = nullptr; + miopenBackendDescriptor_t mBDescriptor = nullptr; + miopenBackendDescriptor_t mYDescriptor = nullptr; + miopenBackendDescriptor_t mTDescriptor = nullptr; + miopenBackendDescriptor_t mDxDescriptor = nullptr; + miopenBackendDescriptor_t mDyDescriptor = nullptr; + +public: + void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + void finalize() override; + void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + OpNode* getOperation() override; + + const OperationPointwise* getOperationPointwise() const { return &mOperationPointwise; } + OperationPointwise* getOperationPointwise() { return &mOperationPointwise; } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/graphapi/reduction.hpp b/src/include/miopen/graphapi/reduction.hpp new file mode 100644 index 0000000000..61029e3daa --- /dev/null +++ b/src/include/miopen/graphapi/reduction.hpp @@ -0,0 +1,168 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include +#include +#include + +namespace miopen { + +namespace graphapi { + +class Reduction +{ +private: + miopenReduceTensorOp_t mReductionOperator = MIOPEN_REDUCE_TENSOR_ADD; + miopenDataType_t mCompType = miopenFloat; + + friend class ReductionBuilder; + +public: + Reduction() noexcept = default; + Reduction(miopenReduceTensorOp_t reductionOperator, miopenDataType_t compType) noexcept + : mReductionOperator(reductionOperator), mCompType(compType) + { + } + + miopenReduceTensorOp_t getReductionOperator() const { return mReductionOperator; } + miopenDataType_t getCompType() const { return mCompType; } +}; + +class ReductionBuilder +{ +private: + Reduction mReduction; + bool mReductionOperatorSet = false; + bool mCompTypeSet = false; + +public: + ReductionBuilder& setReductionOperator(miopenReduceTensorOp_t reductionOperator) noexcept + { + mReduction.mReductionOperator = reductionOperator; + mReductionOperatorSet = true; + return *this; + } + + ReductionBuilder& setCompType(miopenDataType_t compType) noexcept + { + mReduction.mCompType = compType; + mCompTypeSet = true; + return *this; + } + + Reduction build(); +}; + +class BackendReductionDescriptor : public BackendDescriptor +{ +private: + ReductionBuilder mBuilder; + Reduction mReduction; + +public: + void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + void finalize() override; + void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const Reduction* getReduction() const { return &mReduction; } + Reduction* getReduction() { return &mReduction; } +}; + +class OperationReduction : public OpNode +{ +private: + Reduction* mReduction = nullptr; + Tensor* mX = nullptr; + Tensor* mY = nullptr; + + friend class OperationReductionBuilder; + +public: + OperationReduction() noexcept = default; + OperationReduction(Reduction* reduction, Tensor* x, Tensor* y) noexcept + : mReduction(reduction), mX(x), mY(y) + { + } + + Reduction* getReduction() const noexcept { return mReduction; } + Tensor* getX() const noexcept { return mX; } + Tensor* getY() const noexcept { return mY; } + + const std::string& signName() const override; + std::vector getInTensors() const override; + std::vector getOutTensors() const override; +}; + +class OperationReductionBuilder +{ +private: + OperationReduction mOperationReduction; + +public: + OperationReductionBuilder& setReduction(Reduction* reduction); + OperationReductionBuilder& setX(Tensor* x); + OperationReductionBuilder& setY(Tensor* y); + OperationReduction build(); +}; + +class BackendOperationReductionDescriptor : public BackendDescriptor +{ +private: + OperationReductionBuilder mBuilder; + OperationReduction mOperationReduction; + + miopenBackendDescriptor_t mReductionDescriptor = nullptr; + miopenBackendDescriptor_t mXDescriptor = nullptr; + miopenBackendDescriptor_t mYDescriptor = nullptr; + +public: + void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + void finalize() override; + void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + OpNode* getOperation() override; + + const OperationReduction* getOperationReduction() const { return &mOperationReduction; } + OperationReduction* getOperationReduction() { return &mOperationReduction; } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/graphapi/rng.hpp b/src/include/miopen/graphapi/rng.hpp new file mode 100644 index 0000000000..564dc15bc9 --- /dev/null +++ b/src/include/miopen/graphapi/rng.hpp @@ -0,0 +1,211 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace miopen { + +namespace graphapi { + +class Rng +{ +private: + miopenRngDistribution_t mDistribution = MIOPEN_RNG_DISTRIBUTION_BERNOULLI; + double mNormalMean = -1.0; + double mNormalStdev = -1.0; + double mUniformMin = -1.0; + double mUniformMax = -1.0; + double mBernoulliProb = -1.0; + +public: + Rng() = default; + Rng(miopenRngDistribution_t distribution, + double normalMean, + double normalStdev, + double uniformMin, + double uniformMax, + double bernoulliProb) + : mDistribution(distribution), + mNormalMean(normalMean), + mNormalStdev(normalStdev), + mUniformMin(uniformMin), + mUniformMax(uniformMax), + mBernoulliProb(bernoulliProb) + { + } + + miopenRngDistribution_t getDistribution() const noexcept { return mDistribution; } + double getNormalMean() const noexcept { return mNormalMean; } + double getNormalStdev() const noexcept { return mNormalStdev; } + double getUniformMin() const noexcept { return mUniformMin; } + double getUniformMax() const noexcept { return mUniformMax; } + double getBernoulliProb() const noexcept { return mBernoulliProb; } + +private: + friend class RngBuilder; +}; + +class RngBuilder +{ +private: + Rng mRng; + +public: + RngBuilder& setDistribution(miopenRngDistribution_t distribution) noexcept + { + mRng.mDistribution = distribution; + return *this; + } + RngBuilder& setNormalMean(double normalMean) noexcept + { + mRng.mNormalMean = normalMean; + return *this; + } + + RngBuilder& setNormalStdev(double normalStdev); + + RngBuilder& setUniformMin(double uniformMin) noexcept + { + mRng.mUniformMin = uniformMin; + return *this; + } + RngBuilder& setUniformMax(double uniformMax) noexcept + { + mRng.mUniformMax = uniformMax; + return *this; + } + + RngBuilder& setBernoulliProb(double bernoulliProb); + + Rng build() const; +}; + +class BackendRngDescriptor : public BackendDescriptor +{ +private: + RngBuilder mBuilder; + Rng mRng; + +public: + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const Rng* getRng() const noexcept { return &mRng; } + Rng* getRng() noexcept { return &mRng; } +}; + +class OperationRng : public OpNode +{ +private: + Rng* mRng = nullptr; + Tensor* mOutput = nullptr; + std::variant mSeed = 0; // Don't change the order of variant alternatives + Tensor* mOffset = nullptr; + + friend class OperationRngBuilder; + +public: + OperationRng() noexcept = default; + OperationRng(Rng* rng, Tensor* output, int64_t seed, Tensor* offset) noexcept + : mRng(rng), mOutput(output), mSeed(seed), mOffset(offset) + { + } + OperationRng(Rng* rng, Tensor* output, Tensor* seed, Tensor* offset) noexcept + : mRng(rng), mOutput(output), mSeed(seed), mOffset(offset) + { + } + + Rng* getRng() const noexcept { return mRng; } + Tensor* getOutput() const noexcept { return mOutput; } + std::variant getSeed() const noexcept { return mSeed; } + Tensor* getOffset() const noexcept { return mOffset; } + + virtual const std::string& signName() const override; + virtual std::vector getInTensors() const override; + virtual std::vector getOutTensors() const override; +}; + +class OperationRngBuilder +{ +private: + OperationRng mOperationRng; + +public: + OperationRngBuilder& setRng(Rng* rng); + OperationRngBuilder& setOutput(Tensor* output); + OperationRngBuilder& setSeed(int64_t seed) noexcept; + OperationRngBuilder& setSeed(Tensor* seed); + OperationRngBuilder& setOffset(Tensor* offset); + + OperationRng build(); +}; + +class BackendOperationRngDescriptor : public BackendDescriptor +{ +private: + OperationRngBuilder mBuilder; + OperationRng mOperationRng; + miopenBackendDescriptor_t mRngDescriptor = nullptr; + miopenBackendDescriptor_t mOutputDescriptor = nullptr; // sometimes called Y + miopenBackendDescriptor_t mSeedDescriptor = nullptr; + miopenBackendDescriptor_t mOffsetDescriptor = nullptr; + +public: + void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + void finalize() override; + void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + OpNode* getOperation() override; + + const OperationRng* getRng() const { return &mOperationRng; } + OperationRng* getRng() { return &mOperationRng; } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/graphapi/tensor.hpp b/src/include/miopen/graphapi/tensor.hpp new file mode 100644 index 0000000000..888b4a2f39 --- /dev/null +++ b/src/include/miopen/graphapi/tensor.hpp @@ -0,0 +1,167 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include +#include + +#include +#include + +namespace miopen { + +namespace graphapi { + +class Tensor +{ +private: + std::vector mDimensions; + std::vector mStrides; + int64_t mId = 0; + miopenDataType_t mDataType = miopenFloat; + bool mVirtual = false; + +public: + Tensor() noexcept = default; + Tensor(const Tensor&) = default; + Tensor(Tensor&&) noexcept = default; + Tensor& operator=(const Tensor&) = default; + Tensor& operator=(Tensor&&) noexcept = default; + Tensor(miopenDataType_t dataType, + const std::vector& dimensions, + const std::vector& strides, + int64_t id, + bool isVirtual) + : mDimensions(dimensions), + mStrides(strides), + mId(id), + mDataType(dataType), + mVirtual(isVirtual) + { + } + Tensor(miopenDataType_t dataType, + std::vector&& dimensions, + std::vector&& strides, + int64_t id, + bool isVirtual) noexcept + : mDimensions(std::move(dimensions)), + mStrides(std::move(strides)), + mId(id), + mDataType(dataType), + mVirtual(isVirtual) + { + } + + operator miopen::TensorDescriptor() const + { + return {mDataType, + std::vector(mDimensions.cbegin(), mDimensions.cbegin()), + std::vector(mStrides.cbegin(), mStrides.cbegin())}; + } + + miopenDataType_t getDataType() const noexcept { return mDataType; } + const std::vector& getDimensions() const noexcept { return mDimensions; } + const std::vector& getStrides() const noexcept { return mStrides; } + int64_t getId() const noexcept { return mId; } + bool isVirtual() const noexcept { return mVirtual; } +}; + +class TensorBuilder +{ +private: + std::vector mDimensions; + std::vector mStrides; + int64_t mId = 0; + miopenDataType_t mDataType = miopenFloat; + bool mVirtual = false; + bool mUniqueIdSet = false; + bool mDataTypeSet = false; + bool mDimensionsSet = false; + bool mStridesSet = false; + +public: + TensorBuilder& setDataType(miopenDataType_t dataType) &; + TensorBuilder& setDim(const std::vector& dimensions) &; + TensorBuilder& setDim(std::vector&& dimensions) &; + TensorBuilder& setStride(const std::vector& strides) &; + TensorBuilder& setStride(std::vector&& strides) &; + TensorBuilder& setId(int64_t id) &; + TensorBuilder& setVirtual(bool isVirtual) &; + + TensorBuilder&& setDataType(miopenDataType_t dataType) && + { + return std::move(setDataType(dataType)); + } + TensorBuilder&& setDim(const std::vector& dimensions) && + { + return std::move(setDim(dimensions)); + } + TensorBuilder&& setDim(std::vector&& dimensions) && + { + return std::move(setDim(std::move(dimensions))); + } + TensorBuilder&& setStride(const std::vector& strides) && + { + return std::move(setStride(strides)); + } + TensorBuilder&& setStride(std::vector&& strides) && + { + return std::move(setStride(std::move(strides))); + } + TensorBuilder&& setId(int64_t id) && { return std::move(setId(id)); } + TensorBuilder&& setVirtual(bool isVirtual) && { return std::move(setVirtual(isVirtual)); } + + Tensor build() const&; + Tensor build() &&; +}; + +class BackendTensorDescriptor : public BackendDescriptor +{ +private: + TensorBuilder mBuilder; + Tensor mDescriptor; + +public: + BackendTensorDescriptor() = default; + virtual ~BackendTensorDescriptor() override; + virtual void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + virtual void finalize() override; + virtual void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const Tensor* getTensor() const { return &mDescriptor; } + Tensor* getTensor() { return &mDescriptor; } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/graphapi/variant_pack.hpp b/src/include/miopen/graphapi/variant_pack.hpp new file mode 100644 index 0000000000..e56d5365d6 --- /dev/null +++ b/src/include/miopen/graphapi/variant_pack.hpp @@ -0,0 +1,234 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace miopen { + +namespace graphapi { + +namespace detail { + +template +bool noRepetitions(const Range& r) +{ + std::set>> uniqueSet; + bool isUnique = true; + for(auto it = r.begin(), end = r.end(); isUnique && it != end; ++it) + { + std::tie(std::ignore, isUnique) = uniqueSet.insert(*it); + } + return isUnique; +} + +} // namespace detail + +class VariantPack +{ +private: + std::vector mTensorIds; + std::vector mDataPointers; + void* mWorkspace = nullptr; + +public: + VariantPack() noexcept = default; + VariantPack(const VariantPack&) = default; + VariantPack(VariantPack&&) noexcept = default; + VariantPack& operator=(const VariantPack&) = default; + VariantPack& operator=(VariantPack&&) noexcept = default; + VariantPack(const std::vector& tensorIds, + const std::vector& dataPointers, + void* workspace) + : mTensorIds(tensorIds), mDataPointers(dataPointers), mWorkspace(workspace) + { + } + VariantPack(std::vector&& tensorIds, + std::vector&& dataPointers, + void* workspace) + : mTensorIds(std::move(tensorIds)), + mDataPointers(std::move(dataPointers)), + mWorkspace(workspace) + { + } + + void* getDataPointer(int64_t tensorId) const + { + assert(mTensorIds.size() == mDataPointers.size()); + auto iter = std::find(mTensorIds.cbegin(), mTensorIds.cend(), tensorId); + MIOPEN_THROW_IF(iter == mTensorIds.cend(), "No such tensor id in VariantPack"); + return *(mDataPointers.cbegin() + (iter - mTensorIds.cbegin())); + } + void* getWorkspace() const noexcept { return mWorkspace; } + +private: + friend class VariantPackBuilder; + friend class BackendVariantPackDescriptor; +}; + +class VariantPackBuilder +{ +private: + VariantPack mVariantPack; + bool mTensorIdsSet = false; + bool mDataPointersSet = false; + +public: + VariantPackBuilder& setTensorIds(const std::vector& tensorIds) & + { + if(!detail::noRepetitions(tensorIds)) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mVariantPack.mTensorIds = tensorIds; + mTensorIdsSet = true; + return *this; + } + VariantPackBuilder& setTensorIds(std::vector&& tensorIds) & + { + if(!detail::noRepetitions(tensorIds)) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mVariantPack.mTensorIds = std::move(tensorIds); + mTensorIdsSet = true; + return *this; + } + VariantPackBuilder& setDataPointers(const std::vector& dataPointers) & + { + if(miopen::any_of(dataPointers, [](const auto& v) { return v == nullptr; }) || + !detail::noRepetitions(dataPointers)) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mVariantPack.mDataPointers = dataPointers; + mDataPointersSet = true; + return *this; + } + VariantPackBuilder& setDataPointers(std::vector&& dataPointers) & + { + if(miopen::any_of(dataPointers, [](const auto& v) { return v == nullptr; }) || + !detail::noRepetitions(dataPointers)) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mVariantPack.mDataPointers = std::move(dataPointers); + mDataPointersSet = true; + return *this; + } + VariantPackBuilder& setWorkspace(void* workspace) & + { + if(workspace == nullptr) + { + MIOPEN_THROW(miopenStatusBadParm); + } + + mVariantPack.mWorkspace = workspace; + return *this; + } + + VariantPackBuilder&& setTensorIds(const std::vector& tensorIds) && + { + return std::move(setTensorIds(tensorIds)); + } + VariantPackBuilder&& setTensorIds(std::vector&& tensorIds) && + { + return std::move(setTensorIds(std::move(tensorIds))); + } + VariantPackBuilder&& setDataPointers(const std::vector& dataPointers) && + { + return std::move(setDataPointers(dataPointers)); + } + VariantPackBuilder&& setDataPointers(std::vector&& dataPointers) && + { + return std::move(setDataPointers(std::move(dataPointers))); + } + VariantPackBuilder&& setWorkspace(void* workspace) && + { + return std::move(setWorkspace(workspace)); + } + + VariantPack build() const& + { + if(!validate()) + MIOPEN_THROW(miopenStatusBadParm); + return mVariantPack; + } + VariantPack build() && + { + if(!validate()) + MIOPEN_THROW(miopenStatusBadParm); + return std::move(mVariantPack); + } + +private: + bool validate() const + { + return mTensorIdsSet && mDataPointersSet && mVariantPack.mWorkspace != nullptr && + mVariantPack.mTensorIds.size() == mVariantPack.mDataPointers.size() && + std::find(mVariantPack.mDataPointers.cbegin(), + mVariantPack.mDataPointers.cend(), + mVariantPack.mWorkspace) == mVariantPack.mDataPointers.cend(); + } +}; + +class BackendVariantPackDescriptor : public BackendDescriptor +{ +private: + VariantPackBuilder mBuilder; + VariantPack mVariantPack; + +public: + void virtual setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override; + void virtual finalize() override; + void virtual getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override; + + const VariantPack* getVariantPack() const { return &mVariantPack; } + VariantPack* getVariantPack() { return &mVariantPack; } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/src/include/miopen/hipoc_kernel.hpp b/src/include/miopen/hipoc_kernel.hpp index dc29c8d309..cb42faf3a1 100644 --- a/src/include/miopen/hipoc_kernel.hpp +++ b/src/include/miopen/hipoc_kernel.hpp @@ -191,7 +191,7 @@ struct HIPOCKernel { MIOPEN_THROW_HIP_STATUS(status, "Failed to get function: " + kernel_module + " from " + - program.GetCodeObjectPathname().string()); + program.GetCodeObjectPathname()); } } diff --git a/src/include/miopen/hipoc_program.hpp b/src/include/miopen/hipoc_program.hpp index 7e83cc54b7..b8a04ae63c 100644 --- a/src/include/miopen/hipoc_program.hpp +++ b/src/include/miopen/hipoc_program.hpp @@ -50,14 +50,14 @@ struct HIPOCProgram const TargetProperties& target, const std::string& kernel_src); HIPOCProgram(const std::string& program_name, const fs::path& hsaco); - HIPOCProgram(const std::string& program_name, const std::string& hsaco); + HIPOCProgram(const std::string& program_name, const std::vector& hsaco); std::shared_ptr impl; hipModule_t GetModule() const; /// \return Pathname of CO file, if it resides on the filesystem. /// This function should not be called after FreeCodeObjectFileStorage(). fs::path GetCodeObjectPathname() const; /// \return Copy of in-memory CO blob. - std::string GetCodeObjectBlob() const; + std::vector GetCodeObjectBlob() const; /// \return True if CO blob resides in-memory. /// False if CO resides on filesystem. bool IsCodeObjectInMemory() const; diff --git a/src/include/miopen/hipoc_program_impl.hpp b/src/include/miopen/hipoc_program_impl.hpp index c216825799..7af4a1b7cc 100644 --- a/src/include/miopen/hipoc_program_impl.hpp +++ b/src/include/miopen/hipoc_program_impl.hpp @@ -41,7 +41,7 @@ struct HIPOCProgramImpl HIPOCProgramImpl(){}; HIPOCProgramImpl(const std::string& program_name, const fs::path& filespec); - HIPOCProgramImpl(const std::string& program_name, const std::string& blob); + HIPOCProgramImpl(const std::string& program_name, const std::vector& blob); HIPOCProgramImpl(const std::string& program_name, std::string params, diff --git a/src/include/miopen/kern_db.hpp b/src/include/miopen/kern_db.hpp index c3415f7ef7..14bb03a46f 100644 --- a/src/include/miopen/kern_db.hpp +++ b/src/include/miopen/kern_db.hpp @@ -48,7 +48,7 @@ struct KernelConfig static std::string table_name() { return "kern_db"; } std::string kernel_name; std::string kernel_args; - std::string kernel_blob; + std::vector kernel_blob; static std::vector FieldNames() { return {"kernel_name", "kernel_args", "kernel_blob"}; @@ -80,16 +80,17 @@ struct KernelConfig class KernDb : public SQLiteBase { - std::function compress_fn; - std::function decompress_fn; + std::function(const std::vector&, bool*)> compress_fn; + std::function(const std::vector&, unsigned int)> decompress_fn; public: - KernDb(const std::string& filename_, bool is_system); + KernDb(DbKinds db_kind, const std::string& filename_, bool is_system); // This constructor is only intended for testing - KernDb(const std::string& filename_, + KernDb(DbKinds db_kind, + const std::string& filename_, bool is_system_, - std::function compress_fn_, - std::function decompress_fn_); + std::function(const std::vector&, bool*)> compress_fn_, + std::function(const std::vector&, unsigned int)> decompress_fn_); template bool RemoveRecordUnsafe(const T& problem_config) { @@ -111,7 +112,7 @@ class KernDb : public SQLiteBase } template - boost::optional FindRecordUnsafe(const T& problem_config) + boost::optional> FindRecordUnsafe(const T& problem_config) { if(filename.empty()) return boost::none; @@ -124,10 +125,10 @@ class KernDb : public SQLiteBase auto rc = stmt.Step(sql); if(rc == SQLITE_ROW) { - auto compressed_blob = stmt.ColumnBlob(0); - auto md5_hash = stmt.ColumnText(1); - auto uncompressed_size = stmt.ColumnInt64(2); - std::string& decompressed_blob = compressed_blob; + auto compressed_blob = stmt.ColumnBlob(0); + auto md5_hash = stmt.ColumnText(1); + auto uncompressed_size = stmt.ColumnInt64(2); + std::vector& decompressed_blob = compressed_blob; if(uncompressed_size != 0) { decompressed_blob = decompress_fn(compressed_blob, uncompressed_size); diff --git a/src/include/miopen/load_file.hpp b/src/include/miopen/load_file.hpp index 97b86406b2..6394f43196 100644 --- a/src/include/miopen/load_file.hpp +++ b/src/include/miopen/load_file.hpp @@ -1,13 +1,38 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + #ifndef MIOPEN_GUARD_MLOPEN_LOAD_FILE_HPP #define MIOPEN_GUARD_MLOPEN_LOAD_FILE_HPP #include -#include +#include namespace miopen { -std::string LoadFile(const std::string& s); -std::string LoadFile(const fs::path& p); +std::vector LoadFile(const fs::path& path); } // namespace miopen diff --git a/src/include/miopen/lock_file.hpp b/src/include/miopen/lock_file.hpp index 116a20e693..77313e5052 100644 --- a/src/include/miopen/lock_file.hpp +++ b/src/include/miopen/lock_file.hpp @@ -42,6 +42,7 @@ #include #include #include +#include namespace miopen { @@ -75,7 +76,7 @@ class LockFile } void lock() { - LockOperation("lock", MIOPEN_GET_FN_NAME(), [&]() { std::lock(access_mutex, flock); }); + LockOperation("lock", MIOPEN_GET_FN_NAME, [&]() { std::lock(access_mutex, flock); }); } void lock_shared() @@ -83,7 +84,7 @@ class LockFile access_mutex.lock_shared(); try { - LockOperation("shared lock", MIOPEN_GET_FN_NAME(), [&]() { flock.lock_sharable(); }); + LockOperation("shared lock", MIOPEN_GET_FN_NAME, [&]() { flock.lock_sharable(); }); } catch(...) { @@ -93,9 +94,8 @@ class LockFile bool try_lock() { - return TryLockOperation("lock", MIOPEN_GET_FN_NAME(), [&]() { - return std::try_lock(access_mutex, flock) != 0; - }); + return TryLockOperation( + "lock", MIOPEN_GET_FN_NAME, [&]() { return std::try_lock(access_mutex, flock) != 0; }); } bool try_lock_shared() @@ -104,7 +104,7 @@ class LockFile return false; if(TryLockOperation( - "shared lock", MIOPEN_GET_FN_NAME(), [&]() { return flock.try_lock_sharable(); })) + "shared lock", MIOPEN_GET_FN_NAME, [&]() { return flock.try_lock_sharable(); })) return true; access_mutex.unlock(); return false; @@ -112,13 +112,13 @@ class LockFile void unlock() { - LockOperation("unlock", MIOPEN_GET_FN_NAME(), [&]() { flock.unlock(); }); + LockOperation("unlock", MIOPEN_GET_FN_NAME, [&]() { flock.unlock(); }); access_mutex.unlock(); } void unlock_shared() { - LockOperation("unlock shared", MIOPEN_GET_FN_NAME(), [&]() { flock.unlock_sharable(); }); + LockOperation("unlock shared", MIOPEN_GET_FN_NAME, [&]() { flock.unlock_sharable(); }); access_mutex.unlock_shared(); } @@ -130,7 +130,7 @@ class LockFile if(!access_mutex.try_lock_for(duration)) return false; - if(TryLockOperation("timed lock", MIOPEN_GET_FN_NAME(), [&]() { + if(TryLockOperation("timed lock", MIOPEN_GET_FN_NAME, [&]() { return flock.timed_lock(ToPTime(duration)); })) return true; @@ -144,7 +144,7 @@ class LockFile if(!access_mutex.try_lock_shared_for(duration)) return false; - if(TryLockOperation("shared timed lock", MIOPEN_GET_FN_NAME(), [&]() { + if(TryLockOperation("shared timed lock", MIOPEN_GET_FN_NAME, [&]() { return flock.timed_lock_sharable(ToPTime(duration)); })) return true; @@ -186,7 +186,7 @@ class LockFile void LogFlockError(const boost::interprocess::interprocess_exception& ex, const std::string& operation, - const std::string& from) const + const std::string_view from) const { // clang-format off MIOPEN_LOG_E_FROM(from, "File <" << path << "> " << operation << " failed. " @@ -196,8 +196,9 @@ class LockFile // clang-format on } - void - LockOperation(const std::string& op_name, const std::string& from, std::function&& op) + void LockOperation(const std::string& op_name, + const std::string_view from, + std::function&& op) { try { @@ -211,7 +212,7 @@ class LockFile } bool TryLockOperation(const std::string& op_name, - const std::string& from, + const std::string_view from, std::function&& op) { try diff --git a/src/include/miopen/logger.hpp b/src/include/miopen/logger.hpp index 95aaa47c0c..3d87049090 100644 --- a/src/include/miopen/logger.hpp +++ b/src/include/miopen/logger.hpp @@ -347,11 +347,21 @@ LogParam(std::ostream& os, std::string name, const std::vector& vec, bool ind #define MIOPEN_LOG_FUNCTION(...) #endif -MIOPEN_EXPORT -std::string LoggingParseFunction(const char* func, const char* pretty_func); +constexpr std::string_view LoggingParseFunction(const std::string_view func, + const std::string_view pretty_func) +{ + if(func != "operator()") + return func; + // lambda + const auto pf_tail = pretty_func.substr(0, pretty_func.find_first_of('(')); + return pf_tail.substr(1 + pf_tail.find_last_of(':')); +} -#define MIOPEN_GET_FN_NAME() \ - (miopen::LoggingParseFunction(__func__, __PRETTY_FUNCTION__)) /* NOLINT */ +#ifdef _MSC_VER +#define MIOPEN_GET_FN_NAME miopen::LoggingParseFunction(__func__, __FUNCSIG__) +#else +#define MIOPEN_GET_FN_NAME miopen::LoggingParseFunction(__func__, __PRETTY_FUNCTION__) +#endif #define MIOPEN_LOG_XQ_CUSTOM(level, disableQuieting, category, fn_name, ...) \ do \ @@ -369,9 +379,9 @@ std::string LoggingParseFunction(const char* func, const char* pretty_func); MIOPEN_LOG_XQ_CUSTOM(level, disableQuieting, LoggingLevelToCString(level), fn_name, __VA_ARGS__) #define MIOPEN_LOG_CUSTOM(level, category, ...) \ - MIOPEN_LOG_XQ_CUSTOM(level, false, category, MIOPEN_GET_FN_NAME(), __VA_ARGS__) -#define MIOPEN_LOG(level, ...) MIOPEN_LOG_XQ_(level, false, MIOPEN_GET_FN_NAME(), __VA_ARGS__) -#define MIOPEN_LOG_NQ_(level, ...) MIOPEN_LOG_XQ_(level, true, MIOPEN_GET_FN_NAME(), __VA_ARGS__) + MIOPEN_LOG_XQ_CUSTOM(level, false, category, MIOPEN_GET_FN_NAME, __VA_ARGS__) +#define MIOPEN_LOG(level, ...) MIOPEN_LOG_XQ_(level, false, MIOPEN_GET_FN_NAME, __VA_ARGS__) +#define MIOPEN_LOG_NQ_(level, ...) MIOPEN_LOG_XQ_(level, true, MIOPEN_GET_FN_NAME, __VA_ARGS__) #define MIOPEN_LOG_E(...) MIOPEN_LOG(miopen::LoggingLevel::Error, __VA_ARGS__) #define MIOPEN_LOG_E_FROM(from, ...) \ @@ -389,18 +399,22 @@ std::string LoggingParseFunction(const char* func, const char* pretty_func); // Warnings in installable builds, errors otherwise. #define MIOPEN_LOG_WE(...) MIOPEN_LOG(LogWELevel, __VA_ARGS__) -#define MIOPEN_LOG_DRIVER_CMD(...) \ - do \ - { \ - std::ostringstream miopen_driver_cmd_ss; \ - miopen_driver_cmd_ss << miopen::LoggingPrefix() << "Command" \ - << " [" \ - << miopen::LoggingParseFunction(__func__, \ - __PRETTY_FUNCTION__) /* NOLINT */ \ - << "] ./bin/MIOpenDriver " << __VA_ARGS__ << std::endl; \ - std::cerr << miopen_driver_cmd_ss.str(); \ +#define MIOPEN_LOG_DRIVER_COMMAND(driver, ...) \ + do \ + { \ + std::ostringstream miopen_driver_cmd_ss; \ + miopen_driver_cmd_ss << miopen::LoggingPrefix() << "Command" \ + << " [" << MIOPEN_GET_FN_NAME << "] " driver " " << __VA_ARGS__ \ + << std::endl; \ + std::cerr << miopen_driver_cmd_ss.str(); \ } while(false) +#ifdef _WIN32 +#define MIOPEN_LOG_DRIVER_CMD(...) MIOPEN_LOG_DRIVER_COMMAND("MIOpenDriver.exe", __VA_ARGS__) +#else +#define MIOPEN_LOG_DRIVER_CMD(...) MIOPEN_LOG_DRIVER_COMMAND("./bin/MIOpenDriver", __VA_ARGS__) +#endif + #if MIOPEN_LOG_FUNC_TIME_ENABLE class LogScopeTime { @@ -421,7 +435,7 @@ class LogScopeTime std::chrono::time_point m_beg; }; -#define MIOPEN_LOG_SCOPE_TIME const miopen::LogScopeTime miopen_timer(MIOPEN_GET_FN_NAME()) +#define MIOPEN_LOG_SCOPE_TIME const miopen::LogScopeTime miopen_timer(MIOPEN_GET_FN_NAME) #else #define MIOPEN_LOG_SCOPE_TIME #endif diff --git a/src/include/miopen/md5.hpp b/src/include/miopen/md5.hpp index 8772cd0d32..21dad5cd64 100644 --- a/src/include/miopen/md5.hpp +++ b/src/include/miopen/md5.hpp @@ -2,10 +2,12 @@ #define GUARD_MLOPEN_MD5_HPP #include +#include namespace miopen { -std::string md5(std::string s); +std::string md5(const std::string&); +std::string md5(const std::vector&); } // namespace miopen diff --git a/src/include/miopen/mha/invoke_params.hpp b/src/include/miopen/mha/invoke_params.hpp new file mode 100644 index 0000000000..1597344854 --- /dev/null +++ b/src/include/miopen/mha/invoke_params.hpp @@ -0,0 +1,90 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + +#pragma once + +#include +#include +#include "mha.hpp" + +namespace miopen { +namespace mha { + +struct InvokeParams : public miopen::InvokeParams +{ + InvokeParams(const MhaDataForward& dataForward, Data_t ws, std::size_t wsSize) + : mhaDataForwardPtr(std::make_shared(dataForward)), + workSpace(ws), + workSpaceSize(wsSize) + { + } + + InvokeParams(const MhaDataBackward& dataBackward, Data_t ws, std::size_t wsSize) + : mhaDataBackwardPtr(std::make_shared(dataBackward)), + workSpace(ws), + workSpaceSize(wsSize) + { + } + + const MhaDataForward& GetDataForward() const + { + assert(mhaDataForwardPtr); + + if(mhaDataForwardPtr == nullptr) + { + MIOPEN_THROW("Mha InvokeParams GetDataForward() failed: InvokeParams was initialized " + "with a backward direction ctor"); + } + + return *mhaDataForwardPtr; + } + + const MhaDataBackward& GetDataBackward() const + { + assert(mhaDataBackwardPtr); + + if(mhaDataBackwardPtr == nullptr) + { + MIOPEN_THROW("Mha InvokeParams GetDataBackward() failed: InvokeParams was initialized " + "with a forward direction ctor"); + } + + return *mhaDataBackwardPtr; + } + + std::size_t GetWorkspaceSize() const { return workSpaceSize; } + Data_t GetWorkspace() const { return workSpace; } + +private: + std::shared_ptr mhaDataForwardPtr; + std::shared_ptr mhaDataBackwardPtr; + + const Data_t workSpace; + const std::size_t workSpaceSize; +}; + +} // namespace mha +} // namespace miopen diff --git a/src/include/miopen/mha/mha.hpp b/src/include/miopen/mha/mha.hpp new file mode 100644 index 0000000000..296faa6892 --- /dev/null +++ b/src/include/miopen/mha/mha.hpp @@ -0,0 +1,185 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + +#pragma once + +#include + +namespace miopen { +namespace mha { + +struct MhaInputDescsForward +{ + // input tensors + TensorDescriptor kDesc; + TensorDescriptor qDesc; + TensorDescriptor vDesc; + + // input scaling tensors + TensorDescriptor descaleKDesc; + TensorDescriptor descaleQDesc; + TensorDescriptor descaleVDesc; + TensorDescriptor descaleSDesc; + TensorDescriptor scaleSDesc; + TensorDescriptor scaleODesc; + + // input scalars + float scale; + + // input dropout tensors + TensorDescriptor dropoutProbabilityDesc; + TensorDescriptor dropoutSeedDesc; + TensorDescriptor dropoutOffsetDesc; + + // output tensors + TensorDescriptor oDesc; + TensorDescriptor amaxODesc; + TensorDescriptor amaxSDesc; + + // output tensors for training only + TensorDescriptor mDesc; + TensorDescriptor zInvDesc; +}; + +struct MhaInputDescsBackward +{ + // input tensors + TensorDescriptor kDesc; + TensorDescriptor qDesc; + TensorDescriptor vDesc; + + TensorDescriptor oDesc; + TensorDescriptor doDesc; + + // input tensors from fwd pass + TensorDescriptor mDesc; + TensorDescriptor zInvDesc; + + // input scaling tensors + TensorDescriptor descaleKDesc; + TensorDescriptor descaleQDesc; + TensorDescriptor descaleVDesc; + TensorDescriptor descaleSDesc; + TensorDescriptor descaleODesc; + TensorDescriptor descaleDODesc; + TensorDescriptor descaleDSDesc; + + TensorDescriptor scaleSDesc; + TensorDescriptor scaleDSDesc; + TensorDescriptor scaleDQDesc; + TensorDescriptor scaleDKDesc; + TensorDescriptor scaleDVDesc; + + // input scalars + float scale; + + TensorDescriptor dropoutProbabilityDesc; + TensorDescriptor dropoutSeedDesc; + TensorDescriptor dropoutOffsetDesc; + + // output tensors + TensorDescriptor dqDesc; + TensorDescriptor dkDesc; + TensorDescriptor dvDesc; + TensorDescriptor amaxDQDesc; + TensorDescriptor amaxDKDesc; + TensorDescriptor amaxDVDesc; + TensorDescriptor amaxDSDesc; +}; + +struct MhaDataForward +{ + // input tensors + ConstData_t kData; + ConstData_t qData; + ConstData_t vData; + + // input scaling tensors + ConstData_t descaleKData; + ConstData_t descaleQData; + ConstData_t descaleVData; + ConstData_t descaleSData; + ConstData_t scaleSData; + ConstData_t scaleOData; + + ConstData_t dropoutProbabilityData; + ConstData_t dropoutSeedData; + ConstData_t dropoutOffsetData; + + // output tensors + Data_t oData; + Data_t amaxOData; + Data_t amaxSData; + + // output tensors for training only + Data_t mData; + Data_t zInvData; +}; + +struct MhaDataBackward +{ + // input tensors + ConstData_t kData; + ConstData_t qData; + ConstData_t vData; + + ConstData_t oData; + ConstData_t doData; + + // input tensors from fwd pass + ConstData_t mData; + ConstData_t zInvData; + + // input scaling tensors + ConstData_t descaleKData; + ConstData_t descaleQData; + ConstData_t descaleVData; + ConstData_t descaleSData; + ConstData_t descaleOData; + ConstData_t descaleDOData; + ConstData_t descaleDSData; + ConstData_t scaleSData; + ConstData_t scaleDSData; + ConstData_t scaleDQData; + ConstData_t scaleDKData; + ConstData_t scaleDVData; + + ConstData_t dropoutProbabilityData; + ConstData_t dropoutSeedData; + ConstData_t dropoutOffsetData; + + // output tensors + Data_t dqData; + Data_t dkData; + Data_t dvData; + Data_t amaxDQData; + Data_t amaxDKData; + Data_t amaxDVData; + Data_t amaxDSData; +}; + +} // namespace mha +} // namespace miopen diff --git a/src/include/miopen/mha/mha_descriptor.hpp b/src/include/miopen/mha/mha_descriptor.hpp new file mode 100644 index 0000000000..2e65b68972 --- /dev/null +++ b/src/include/miopen/mha/mha_descriptor.hpp @@ -0,0 +1,61 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#ifndef MIOPEN_MHADESCRIPTOR_HPP_ +#define MIOPEN_MHADESCRIPTOR_HPP_ + +#include +#include +#include +#include +#include + +namespace miopen { + +struct Handle; +struct TensorDescriptor; + +struct MhaDescriptor : miopenMhaDescriptor +{ + MhaDescriptor() {} + + void SetParams(float scale_) { scale = scale_; } + + float GetScale() const { return scale; } + + friend std::ostream& operator<<(std::ostream& stream, const MhaDescriptor& x); + + friend void to_json(nlohmann::json& json, const MhaDescriptor& descriptor); + friend void from_json(const nlohmann::json& json, MhaDescriptor& descriptor); + +private: + float scale; +}; + +} // namespace miopen + +MIOPEN_DEFINE_OBJECT(miopenMhaDescriptor, miopen::MhaDescriptor); + +#endif // MIOPEN_MHADESCRIPTOR_HPP_ diff --git a/src/include/miopen/mha/problem_description.hpp b/src/include/miopen/mha/problem_description.hpp new file mode 100644 index 0000000000..ecf8b2129c --- /dev/null +++ b/src/include/miopen/mha/problem_description.hpp @@ -0,0 +1,90 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + +#pragma once + +#include +#include +#include "mha.hpp" + +namespace miopen { + +struct NetworkConfig; + +namespace mha { + +struct ProblemDescription : ProblemDescriptionBase +{ + // softmax forward constructor + ProblemDescription(const MhaInputDescsForward& descs) + : isForward(true), mhaInputDescsForwardPtr(std::make_shared(descs)) + { + } + + // softmax backward constructor + ProblemDescription(const MhaInputDescsBackward& descs) + : isForward(false), mhaInputDescsBackwardPtr(std::make_shared(descs)) + { + } + + bool IsForward() const { return isForward; } + const MhaInputDescsForward& GetDescsForward() const + { + assert(mhaInputDescsForwardPtr && isForward); + + if(mhaInputDescsForwardPtr == nullptr) + { + MIOPEN_THROW("Mha ProblemDescription GetDescsForward() failed: PD was initialized with " + "a backward direction ctor"); + } + + return *mhaInputDescsForwardPtr; + } + + const MhaInputDescsBackward& GetDescsBackward() const + { + assert(mhaInputDescsBackwardPtr && !isForward); + + if(mhaInputDescsBackwardPtr == nullptr) + { + MIOPEN_THROW("Mha ProblemDescription GetDescsBackward() failed: PD was initialized " + "with a forward direction ctor"); + } + + return *mhaInputDescsBackwardPtr; + } + + NetworkConfig MakeNetworkConfig() const override; + +private: + const bool isForward; + + std::shared_ptr mhaInputDescsForwardPtr; + std::shared_ptr mhaInputDescsBackwardPtr; +}; + +} // namespace mha +} // namespace miopen diff --git a/src/include/miopen/mha/solvers.hpp b/src/include/miopen/mha/solvers.hpp new file mode 100644 index 0000000000..8b3e6f45e5 --- /dev/null +++ b/src/include/miopen/mha/solvers.hpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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. + * + *******************************************************************************/ + +#pragma once + +#include +#include + +#include + +namespace miopen { + +namespace solver { + +namespace mha { + +using MhaSolver = NonTunableSolverBase; + +struct MhaForward final : MhaSolver +{ + const std::string& SolverDbId() const override { return GetSolverDbId(); } + + bool IsApplicable(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const override; + + ConvSolution GetSolution(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const override; + + std::size_t GetWorkspaceSize(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const override; + + bool MayNeedWorkspace() const override; +}; + +struct MhaBackward final : MhaSolver +{ + const std::string& SolverDbId() const override { return GetSolverDbId(); } + + bool IsApplicable(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const override; + + ConvSolution GetSolution(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const override; + + std::size_t GetWorkspaceSize(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const override; + + bool MayNeedWorkspace() const override; +}; + +} // namespace mha + +} // namespace solver + +} // namespace miopen diff --git a/src/include/miopen/mlo_internal.hpp b/src/include/miopen/mlo_internal.hpp index 017efd37a1..f8732f8e62 100644 --- a/src/include/miopen/mlo_internal.hpp +++ b/src/include/miopen/mlo_internal.hpp @@ -63,7 +63,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif #include -#if MIOPEN_ENABLE_SQLITE +#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB #include #else #include @@ -134,7 +134,7 @@ class DbTimer; struct AnyInvokeParams; -#if MIOPEN_ENABLE_SQLITE +#if MIOPEN_ENABLE_SQLITE && MIOPEN_USE_SQLITE_PERFDB using PerformanceDb = DbTimer>; #else using PerformanceDb = DbTimer>; diff --git a/src/include/miopen/oclkernel.hpp b/src/include/miopen/oclkernel.hpp index 8c00c64640..6b42aa2b3b 100644 --- a/src/include/miopen/oclkernel.hpp +++ b/src/include/miopen/oclkernel.hpp @@ -149,7 +149,7 @@ class OCLKernel { assert(!gdims.empty() && gdims.size() <= 3); assert(!ldims.empty() && ldims.size() <= 3); - if(std::accumulate(ldims.begin(), ldims.end(), 1, std::multiplies{}) > + if(std::accumulate(ldims.begin(), ldims.end(), 1ULL, std::multiplies{}) > 256) // FIXME: get ldims limit from runtime { std::fill(ldims.begin(), ldims.end(), 0); diff --git a/src/include/miopen/op_kernel_args.hpp b/src/include/miopen/op_kernel_args.hpp index e2ec47c195..5bb4041e46 100644 --- a/src/include/miopen/op_kernel_args.hpp +++ b/src/include/miopen/op_kernel_args.hpp @@ -3,11 +3,7 @@ #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #include struct OpKernelArg diff --git a/src/include/miopen/problem.hpp b/src/include/miopen/problem.hpp index 2900008785..66b12c8e70 100644 --- a/src/include/miopen/problem.hpp +++ b/src/include/miopen/problem.hpp @@ -31,6 +31,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -59,13 +62,20 @@ namespace conv { struct ProblemDescription; } // namespace conv +namespace softmax { +struct ProblemDescription; +} // namespace softmax + struct BiasDescriptor { }; // The order of types is important for deserialization and should be preserved between releases. -using OperatorDescriptor = - boost::variant; +using OperatorDescriptor = boost::variant; struct Problem { @@ -99,6 +109,8 @@ struct Problem conv::ProblemDescription AsConvolution() const; activ::ProblemDescription AsActivation() const; + mha::ProblemDescription AsMha() const; + softmax::ProblemDescription AsSoftmax() const; [[nodiscard]] miopenTensorArgumentId_t GetInputId() const; [[nodiscard]] miopenTensorArgumentId_t GetOutputId() const; @@ -143,18 +155,30 @@ struct Problem friend void from_json(const nlohmann::json& j, Problem& problem); private: + using Buffers = std::unordered_map; + miopenProblemDirection_t direction = miopenProblemDirectionForward; std::unordered_map tensor_descriptors; OperatorDescriptor operator_descriptor; - using Buffers = std::unordered_map; - std::vector FindSolutionsImpl(Handle& handle, const FindOptions& options, std::size_t max_solutions, const Buffers& buffers, const ConvolutionDescriptor& conv_desc) const; + std::vector FindSolutionsImpl(Handle& handle, + const FindOptions& options, + std::size_t max_solutions, + const Buffers& buffers, + const MhaDescriptor& mha_desc) const; + + std::vector FindSolutionsImpl(Handle& handle, + const FindOptions& options, + std::size_t max_solutions, + const Buffers& buffers, + const SoftmaxDescriptor& softmax_desc) const; + void LogDriverCommand(const ConvolutionDescriptor& conv_desc) const; void LogDriverCommand(const ActivationDescriptor& descriptor) const; }; diff --git a/src/include/miopen/ramdb.hpp b/src/include/miopen/ramdb.hpp index 57af3c1979..4fea6e82d7 100644 --- a/src/include/miopen/ramdb.hpp +++ b/src/include/miopen/ramdb.hpp @@ -48,12 +48,16 @@ class LockFile; class RamDb : protected PlainTextDb { public: - RamDb(std::string path, bool is_system, const std::string& /*arch*/, std::size_t /*num_cu*/) - : RamDb(path, is_system) + RamDb(DbKinds db_kind_, + std::string path, + bool is_system, + const std::string& /*arch*/, + std::size_t /*num_cu*/) + : RamDb(db_kind_, path, is_system) { } - RamDb(std::string path, bool is_system = false); + RamDb(DbKinds db_kind_, std::string path, bool is_system = false); RamDb(const RamDb&) = delete; RamDb(RamDb&&) = delete; @@ -61,14 +65,15 @@ class RamDb : protected PlainTextDb RamDb& operator=(RamDb&&) = delete; static std::string GetTimeFilePath(const std::string& path); - static RamDb& GetCached(const std::string& path, bool is_system); + static RamDb& GetCached(DbKinds db_kind_, const std::string& path, bool is_system); - static RamDb& GetCached(const std::string& path, + static RamDb& GetCached(DbKinds db_kind_, + const std::string& path, bool is_system, const std::string& /*arch*/, std::size_t /*num_cu*/) { - return GetCached(path, is_system); + return GetCached(db_kind_, path, is_system); } boost::optional FindRecord(const std::string& problem); @@ -76,7 +81,7 @@ class RamDb : protected PlainTextDb template boost::optional FindRecord(const TProblem& problem) { - const auto key = DbRecord::Serialize(problem); + const auto key = DbRecord::SerializeKey(db_kind, problem); return FindRecord(key); } @@ -97,7 +102,7 @@ class RamDb : protected PlainTextDb template inline bool Remove(const T& problem_config, const std::string& id) { - const auto key = DbRecord::Serialize(problem_config); + const auto key = DbRecord::SerializeKey(db_kind, problem_config); return Remove(key, id); } @@ -112,7 +117,7 @@ class RamDb : protected PlainTextDb inline boost::optional Update(const T& problem_config, const std::string& id, const V& values) { - DbRecord record(problem_config); + DbRecord record(db_kind, problem_config); record.SetValues(id, values); const auto ok = UpdateRecord(record); if(ok) diff --git a/src/include/miopen/readonlyramdb.hpp b/src/include/miopen/readonlyramdb.hpp index 2b213bd8ad..589d7adbfd 100644 --- a/src/include/miopen/readonlyramdb.hpp +++ b/src/include/miopen/readonlyramdb.hpp @@ -43,9 +43,10 @@ extern bool& rordb_embed_fs_override(); class ReadonlyRamDb { public: - ReadonlyRamDb(std::string path) : db_path(path) {} + ReadonlyRamDb(DbKinds db_kind_, const std::string& path) : db_kind(db_kind_), db_path(path) {} - static ReadonlyRamDb& GetCached(const std::string& path, bool warn_if_unreadable); + static ReadonlyRamDb& + GetCached(DbKinds db_kind_, const std::string& path, bool warn_if_unreadable); boost::optional FindRecord(const std::string& problem) const { @@ -74,7 +75,7 @@ class ReadonlyRamDb template boost::optional FindRecord(const TProblem& problem) const { - const auto key = DbRecord::Serialize(problem); + const auto key = DbRecord::SerializeKey(db_kind, problem); return FindRecord(key); } @@ -96,6 +97,7 @@ class ReadonlyRamDb const std::unordered_map& GetCacheMap() const { return cache; } private: + DbKinds db_kind; std::string db_path; std::unordered_map cache; diff --git a/src/include/miopen/reduce_common.hpp b/src/include/miopen/reduce_common.hpp index 162ad64319..37b92e727d 100644 --- a/src/include/miopen/reduce_common.hpp +++ b/src/include/miopen/reduce_common.hpp @@ -26,11 +26,7 @@ #ifndef GUARD_MIOPEN_REDUCE_COMMON_HPP #define GUARD_MIOPEN_REDUCE_COMMON_HPP -#if !defined(_WIN32) #include -#else -#include -#endif #include namespace reduce { diff --git a/src/include/miopen/rnn.hpp b/src/include/miopen/rnn.hpp index 00aadbabdf..b5f5275981 100644 --- a/src/include/miopen/rnn.hpp +++ b/src/include/miopen/rnn.hpp @@ -481,21 +481,22 @@ struct RNNDescriptor : miopenRNNDescriptor Data_t reserveSpace, size_t reserveSpaceSize) const; - void RNNForwardTraining_MS(Handle& handle, - std::vector& seq_array, - const TensorDescriptor& xDesc, - ConstData_t x, - const TensorDescriptor& hxDesc, - ConstData_t hx, - ConstData_t cx, - const TensorDescriptor& wDesc, - ConstData_t w, - const TensorDescriptor& yDesc, - Data_t y, - Data_t hy, - Data_t cy, - Data_t reserveSpace, - size_t reserveSpaceSize) const; + void RNNForwardMS(Handle& handle, + std::vector& seq_array, + const TensorDescriptor& xDesc, + ConstData_t x, + const TensorDescriptor& hxDesc, + ConstData_t hx, + ConstData_t cx, + const TensorDescriptor& wDesc, + ConstData_t w, + const TensorDescriptor& yDesc, + Data_t y, + Data_t hy, + Data_t cy, + Data_t extra_space, + size_t extra_space_size, + miopenRNNFWDMode_t fwd_mode) const; void RNNForwardInferencePacked(Handle& handle, int seqLen, diff --git a/src/include/miopen/rnn_util.hpp b/src/include/miopen/rnn_util.hpp index 84e453358b..92876b8a9a 100644 --- a/src/include/miopen/rnn_util.hpp +++ b/src/include/miopen/rnn_util.hpp @@ -32,29 +32,75 @@ #include #include #include +#include namespace miopen { -#if MIOPEN_BACKEND_HIP -inline void RNNProfilingBegin(const miopen::Handle& handle, - miopen::HipEventPtr& start, - miopen::HipEventPtr& stop) +struct RnnHipAutoProfiler { - start = miopen::make_hip_event(); - stop = miopen::make_hip_event(); - hipEventRecord(start.get(), handle.GetStream()); -} + RnnHipAutoProfiler(Handle& handle) : is_profiling_active(handle.IsProfilingEnabled()) + { + if(is_profiling_active) + { +#if MIOPEN_BACKEND_HIP + attached_handle = &handle; +#endif + RNNProfilingBegin(); + } + } + ~RnnHipAutoProfiler() + { + if(is_profiling_active) + { + RNNProfilingEnd(); + } + } -inline float -RNNProfilingEnd(const miopen::Handle& handle, miopen::HipEventPtr& start, miopen::HipEventPtr& stop) -{ - hipEventRecord(stop.get(), handle.GetStream()); - hipEventSynchronize(stop.get()); - float mS = 0; - hipEventElapsedTime(&mS, start.get(), stop.get()); - return mS; -} + void abortProfiling() + { + if(is_profiling_active) + { +#if MIOPEN_BACKEND_HIP + attached_handle->EnableProfiling(true); +#endif + is_profiling_active = false; + } + } + +private: + void RNNProfilingBegin() + { +#if MIOPEN_BACKEND_HIP + attached_handle->EnableProfiling(false); + start = miopen::make_hip_event(); + stop = miopen::make_hip_event(); + hipEventRecord(start.get(), attached_handle->GetStream()); +#endif + } + + void RNNProfilingEnd() + { +#if MIOPEN_BACKEND_HIP + hipEventRecord(stop.get(), attached_handle->GetStream()); + hipEventSynchronize(stop.get()); + float eventTime_mS = 0; + hipEventElapsedTime(&eventTime_mS, start.get(), stop.get()); + attached_handle->EnableProfiling(true); + attached_handle->ResetKernelTime(); + attached_handle->AccumKernelTime(eventTime_mS); +#endif + } + +#if MIOPEN_BACKEND_HIP + Handle* attached_handle = nullptr; + HipEventPtr start = nullptr; + HipEventPtr stop = nullptr; +#endif + bool is_profiling_active = false; +}; + +#if MIOPEN_BACKEND_HIP inline miopen::HipEventPtr make_hip_fast_event() { hipEvent_t result = nullptr; @@ -147,7 +193,7 @@ struct RNNTensorPaddingConverter size_t total_batch = std::accumulate( desc_array.data, desc_array.data + desc_array.size(), - 0, + 0ULL, [](size_t x, miopenTensorDescriptor_t y) { return x + deref(y).GetLengths()[0]; }); return GetTempPackedBuffersSpace(rnn_desc, total_batch, desc_array[0].GetLengths()[1]); @@ -209,7 +255,7 @@ struct RNNTensorBaseLayoutConverter size_t total_batch = std::accumulate( desc_array.data, desc_array.data + desc_array.size(), - 0, + 0ULL, [](size_t x, miopenTensorDescriptor_t y) { return x + deref(y).GetLengths()[0]; }); return GetTempPackedBuffersSpace(rnn_desc, total_batch, desc_array[0].GetLengths()[1]); @@ -272,6 +318,61 @@ void FillSeqTensorByPaddingMarker(const Handle& handle, const SeqTensorDescriptor& desc, Data_t data); +int getReductionAlgo(); + +inline size_t ReductionWorkspaceSize(const Handle& handle, + size_t batchLenSum, + size_t nHiddenTensorsPerLayer, + size_t workspaceScale, + size_t hsize, + bool is_bidirect, + miopenDataType_t rnn_data_t) +{ + int red_algo = getReductionAlgo(); + + size_t reduction_ws = 0; + + // nothing to reduce, + if(batchLenSum == 1) + return 0; + + if(red_algo == 1) + { + miopen::ReduceTensorDescriptor red_add{ + miopenReduceTensorOp_t::MIOPEN_REDUCE_TENSOR_ADD, + miopenDataType_t::miopenFloat, // compute in float for fp16 + miopenNanPropagation_t::MIOPEN_PROPAGATE_NAN, + miopenReduceTensorIndices_t::MIOPEN_REDUCE_TENSOR_NO_INDICES, + miopenIndicesType_t::MIOPEN_32BIT_INDICES}; + + int bidirect_mp = is_bidirect ? 2 : 1; + + size_t hy_stride = hsize * bidirect_mp * workspaceScale; + + size_t bias_total_cnt = hsize * bidirect_mp * nHiddenTensorsPerLayer; + + const std::vector ws_bias_strides{ + batchLenSum * workspaceScale * hsize * bidirect_mp, hy_stride, 1}; + + const miopen::TensorDescriptor ws_desc{ + rnn_data_t, {1, batchLenSum, bias_total_cnt}, ws_bias_strides}; + + const std::vector dw_bias_strides{bias_total_cnt, bias_total_cnt, 1}; + const miopen::TensorDescriptor dw_desc{rnn_data_t, {1, 1, bias_total_cnt}, dw_bias_strides}; + + reduction_ws = red_add.GetWorkspaceSize(handle, ws_desc, dw_desc) + // WA CK bug + (rnn_data_t == miopenDataType_t::miopenHalf ? 4 : 0); + } + else + { + if(red_algo == 2 || red_algo == 3) + { + reduction_ws = batchLenSum * GetTypeSize(rnn_data_t); + } + } + return reduction_ws; +} + } // namespace miopen #endif // GUARD_MIOPEN_RNN_UTIL_HPP_ diff --git a/src/include/miopen/softmax.hpp b/src/include/miopen/softmax.hpp index b4f8909908..0ed0f300be 100644 --- a/src/include/miopen/softmax.hpp +++ b/src/include/miopen/softmax.hpp @@ -28,12 +28,47 @@ #include #include +#include + +#include namespace miopen { struct Handle; struct TensorDescriptor; +struct SoftmaxDescriptor : miopenSoftmaxDescriptor +{ + SoftmaxDescriptor() {} + + float GetAlpha() const { return alpha; } + float GetBeta() const { return beta; } + miopenSoftmaxAlgorithm_t GetAlgorithm() const { return algorithm; } + miopenSoftmaxMode_t GetMode() const { return mode; } + + void SetParams(float alpha_, + float beta_, + miopenSoftmaxAlgorithm_t algorithm_, + miopenSoftmaxMode_t mode_) + { + alpha = alpha_; + beta = beta_; + algorithm = algorithm_; + mode = mode_; + } + + friend std::ostream& operator<<(std::ostream& stream, const SoftmaxDescriptor& x); + + friend void to_json(nlohmann::json& json, const SoftmaxDescriptor& descriptor); + friend void from_json(const nlohmann::json& json, SoftmaxDescriptor& descriptor); + +private: + float alpha; + float beta; + miopenSoftmaxAlgorithm_t algorithm; + miopenSoftmaxMode_t mode; +}; + miopenStatus_t SoftmaxForward(Handle& handle, const void* alpha, const void* beta, @@ -62,4 +97,7 @@ miopenStatus_t SoftmaxBackward(Handle& handle, int dx_offset = 0); } // namespace miopen + +MIOPEN_DEFINE_OBJECT(miopenSoftmaxDescriptor, miopen::SoftmaxDescriptor); + #endif // _MIOPEN_SOFTMAX_HPP_ diff --git a/src/include/miopen/softmax/invoke_params.hpp b/src/include/miopen/softmax/invoke_params.hpp index c2792929ab..f2d6240e98 100644 --- a/src/include/miopen/softmax/invoke_params.hpp +++ b/src/include/miopen/softmax/invoke_params.hpp @@ -74,9 +74,9 @@ struct InvokeParams : public miopen::InvokeParams Data_t dx_, miopenSoftmaxAlgorithm_t algorithm_, miopenSoftmaxMode_t mode_, - int y_offset_, - int dy_offset_, - int dx_offset_) + int y_offset_ = 0, + int dy_offset_ = 0, + int dx_offset_ = 0) : algorithm(algorithm_), mode(mode_), diff --git a/src/include/miopen/solution.hpp b/src/include/miopen/solution.hpp index 4fab925bf2..e60d085ee3 100644 --- a/src/include/miopen/solution.hpp +++ b/src/include/miopen/solution.hpp @@ -111,6 +111,18 @@ struct Solution : miopenSolution std::size_t workspace_size, const ConvolutionDescriptor& conv_desc); + void RunImpl(Handle& handle, + const std::unordered_map& inputs, + Data_t /*workspace*/, + std::size_t /*workspace_size*/, + const SoftmaxDescriptor& softmax_desc); + + void RunImpl(Handle& handle, + const std::unordered_map& inputs, + Data_t workspace, + std::size_t workspace_size, + const MhaDescriptor& mha_desc); + void RunImpl(Handle& handle, const std::unordered_map& inputs, Data_t workspace, diff --git a/src/include/miopen/solver.hpp b/src/include/miopen/solver.hpp index fd2fcfb0f9..b626a5702d 100644 --- a/src/include/miopen/solver.hpp +++ b/src/include/miopen/solver.hpp @@ -4506,7 +4506,7 @@ struct PerformanceConfigHipImplicitGemmGroupFwdXdlops bool RunParameterPredictionModel(const ExecutionContext& ctx, const miopen::conv::ProblemDescription& problem); void InitHeuristicKernelIDs(); - bool ModelApplyToken(int idx, std::string value); + bool ModelApplyToken(int idx, std::string value, const std::string& arch); #endif template void Init(const miopen::conv::ProblemDescription&); @@ -4631,7 +4631,7 @@ struct ConvHipImplicitGemm3DGroupFwdXdlops final }; struct PerformanceConfigHipImplicitGemm3DGroupWrwXdlops - : PerfConfigBase + : PerfConfigBaseCK { int index; std::string kernel_id; @@ -4656,11 +4656,6 @@ struct PerformanceConfigHipImplicitGemm3DGroupWrwXdlops return IsValid(problem); } bool IsValid(const miopen::conv::ProblemDescription&) const; - template - static void Visit(Self&& s, F f) - { - f(s.kernel_id, "kernel_id"); - } bool operator==(const PerformanceConfigHipImplicitGemm3DGroupWrwXdlops& other) const; private: @@ -4712,7 +4707,7 @@ struct ConvHipImplicitGemm3DGroupWrwXdlops final }; struct PerformanceConfigHipImplicitGemm3DGroupBwdXdlops - : PerfConfigBase + : PerfConfigBaseCK { int index; std::string kernel_id; @@ -4737,11 +4732,6 @@ struct PerformanceConfigHipImplicitGemm3DGroupBwdXdlops return IsValid(problem); } bool IsValid(const miopen::conv::ProblemDescription&) const; - template - static void Visit(Self&& s, F f) - { - f(s.kernel_id, "kernel_id"); - } bool operator==(const PerformanceConfigHipImplicitGemm3DGroupBwdXdlops& other) const; private: @@ -4793,7 +4783,7 @@ struct ConvHipImplicitGemm3DGroupBwdXdlops final }; struct PerformanceConfigHipImplicitGemmGroupBwdXdlops - : PerfConfigBase + : PerfConfigBaseCK { int index; std::string kernel_id; @@ -4810,7 +4800,8 @@ struct PerformanceConfigHipImplicitGemmGroupBwdXdlops : PerformanceConfigHipImplicitGemmGroupBwdXdlops(0, "") { } - void HeuristicInit(const miopen::conv::ProblemDescription&); + + void HeuristicInit(const ExecutionContext&, const miopen::conv::ProblemDescription&); bool SetNextValue(const miopen::conv::ProblemDescription&); bool IsValidValue() const; bool IsValid(const ExecutionContext&, const miopen::conv::ProblemDescription& problem) const @@ -4818,14 +4809,20 @@ struct PerformanceConfigHipImplicitGemmGroupBwdXdlops return IsValid(problem); } bool IsValid(const miopen::conv::ProblemDescription&) const; - template - static void Visit(Self&& s, F f) - { - f(s.kernel_id, "kernel_id"); - } bool operator==(const PerformanceConfigHipImplicitGemmGroupBwdXdlops& other) const; + bool IsModelApplicable(const ExecutionContext& ctx, + const miopen::conv::ProblemDescription& problem) const; private: +#if MIOPEN_ENABLE_AI_KERNEL_TUNING + std::vector heuristic_indexes; + std::vector> heuristic_kernels; + template + bool RunParameterPredictionModel(const ExecutionContext& ctx, + const miopen::conv::ProblemDescription& problem); + void InitHeuristicKernelIDs(); + bool ModelApplyToken(int idx, std::string value); +#endif template void Init(const miopen::conv::ProblemDescription&); template @@ -4873,7 +4870,7 @@ struct ConvHipImplicitGemmGroupBwdXdlops final }; struct PerformanceConfigHipImplicitGemmGroupWrwXdlops - : PerfConfigBase + : PerfConfigBaseCK { int index; std::string kernel_id; @@ -4890,7 +4887,7 @@ struct PerformanceConfigHipImplicitGemmGroupWrwXdlops : PerformanceConfigHipImplicitGemmGroupWrwXdlops(0, "") { } - void HeuristicInit(const miopen::conv::ProblemDescription&); + void HeuristicInit(const ExecutionContext&, const miopen::conv::ProblemDescription&); bool SetNextValue(const miopen::conv::ProblemDescription&); bool IsValidValue() const; bool IsValid(const ExecutionContext&, const miopen::conv::ProblemDescription& problem) const @@ -4898,14 +4895,20 @@ struct PerformanceConfigHipImplicitGemmGroupWrwXdlops return IsValid(problem); } bool IsValid(const miopen::conv::ProblemDescription&) const; - template - static void Visit(Self&& s, F f) - { - f(s.kernel_id, "kernel_id"); - } bool operator==(const PerformanceConfigHipImplicitGemmGroupWrwXdlops& other) const; + bool IsModelApplicable(const ExecutionContext& ctx, + const miopen::conv::ProblemDescription& problem) const; private: +#if MIOPEN_ENABLE_AI_KERNEL_TUNING + std::vector heuristic_indexes; + std::vector> heuristic_kernels; + template + bool RunParameterPredictionModel(const ExecutionContext& ctx, + const miopen::conv::ProblemDescription& problem); + void InitHeuristicKernelIDs(); + bool ModelApplyToken(int idx, std::string value); +#endif template void Init(const miopen::conv::ProblemDescription&); template @@ -5025,7 +5028,7 @@ struct ConvHipImplicitGemmF16F8F16FwdXdlops final }; struct PerformanceConfigHipImplicitGemmF16F8F16BwdXdlops - : PerfConfigBase + : PerfConfigBaseCK { int index; std::string kernel_id; @@ -5050,11 +5053,6 @@ struct PerformanceConfigHipImplicitGemmF16F8F16BwdXdlops return IsValid(problem); } bool IsValid(const miopen::conv::ProblemDescription&) const; - template - static void Visit(Self&& s, F f) - { - f(s.kernel_id, "kernel_id"); - } bool operator==(const PerformanceConfigHipImplicitGemmF16F8F16BwdXdlops& other) const; private: @@ -5102,7 +5100,7 @@ struct ConvHipImplicitGemmF16F8F16BwdXdlops final }; struct PerformanceConfigHipImplicitGemmF16F8F16WrwXdlops - : PerfConfigBase + : PerfConfigBaseCK { int index; std::string kernel_id; @@ -5127,11 +5125,6 @@ struct PerformanceConfigHipImplicitGemmF16F8F16WrwXdlops return IsValid(problem); } bool IsValid(const miopen::conv::ProblemDescription&) const; - template - static void Visit(Self&& s, F f) - { - f(s.kernel_id, "kernel_id"); - } bool operator==(const PerformanceConfigHipImplicitGemmF16F8F16WrwXdlops& other) const; private: diff --git a/src/include/miopen/solver/gemm_common.hpp b/src/include/miopen/solver/gemm_common.hpp new file mode 100644 index 0000000000..d4b4568da9 --- /dev/null +++ b/src/include/miopen/solver/gemm_common.hpp @@ -0,0 +1,63 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once +#ifndef GUARD_MIOPEN_SOLVER_GEMM_COMMON_HPP +#define GUARD_MIOPEN_SOLVER_GEMM_COMMON_HPP + +#include +#include +#include +#include + +#include + +namespace miopen { +namespace solver { +namespace conv { +namespace gemm { + +std::size_t MaxMemAllocSz(Handle& h, + const miopen::conv::ProblemDescription& problem, + bool double_limit_for_fp32 = false); + +constexpr bool IsBf16Supported = MIOPEN_USE_ROCBLAS; +constexpr bool IsFp16Supported = MIOPEN_USE_ROCBLAS; + +bool IsAnyBufferBf16(const TensorDescriptor& xDesc, + const TensorDescriptor& yDesc, + const TensorDescriptor& wDesc); +bool IsAnyBufferFp16(const TensorDescriptor& xDesc, + const TensorDescriptor& yDesc, + const TensorDescriptor& wDesc); + +double SlowdownFactor(int n_oper, double oper_factor, double multiple_oper_factor); + +} // namespace gemm +} // namespace conv +} // namespace solver +} // namespace miopen + +#endif // GUARD_MIOPEN_SOLVER_GEMM_COMMON_HPP diff --git a/src/include/miopen/solver/implicitgemm_ck_util.hpp b/src/include/miopen/solver/implicitgemm_ck_util.hpp index 2199d88b01..efb0d16b96 100644 --- a/src/include/miopen/solver/implicitgemm_ck_util.hpp +++ b/src/include/miopen/solver/implicitgemm_ck_util.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #if MIOPEN_USE_COMPOSABLEKERNEL #include @@ -96,6 +97,31 @@ bool IsCKApplicable(const ProblemDescriptionType& problem) ptrs.begin(), ptrs.end(), [&args](auto& ptr) { return args.IsSupportedBy(ptr); }); } +#define WORKAROUND_CK_ISSUE_1184 1 +#if WORKAROUND_CK_ISSUE_1184 +struct HipEventProfiler +{ + const Handle& handle; + float event_time; + HipEventPtr start; + HipEventPtr stop; + + HipEventProfiler(const Handle& handle_) + : handle(handle_), event_time(0.0f), start(make_hip_event()), stop(make_hip_event()) + { + hipEventRecord(start.get(), handle.GetStream()); + } + ~HipEventProfiler() + { + hipEventRecord(stop.get(), handle.GetStream()); + hipEventSynchronize(stop.get()); + hipEventElapsedTime(&event_time, start.get(), stop.get()); + handle.ResetKernelTime(); + handle.AccumKernelTime(event_time); + } +}; +#endif + template (); auto argument_ptr = ck_args.MakeArgPtr(sh_conv_ptr, data_ctx.tensors); auto invoker_ptr = sh_conv_ptr->MakeInvokerPointer(); - - const auto enable_profiling = handle.IsProfilingEnabled(); - float elapsed_time = - invoker_ptr->Run(argument_ptr.get(), {handle.GetStream(), enable_profiling}); - if(enable_profiling) { - handle.ResetKernelTime(); - handle.AccumKernelTime(elapsed_time); + HipEventProfiler pfr(handle); + if constexpr(std::is_same::value) + { + auto zero = 0.0f; + const auto& tensors = data_ctx.tensors; + SetTensor(handle, tensors.dwDesc, tensors.dw, &zero); + } + invoker_ptr->Run(argument_ptr.get(), {handle.GetStream(), false}); } }; }; @@ -605,15 +632,14 @@ ConvSolution InitInvokerFactoryNCHW(const ExecutionContext& ctx, std::swap(conv_tensors.x, conv_tensors.y); std::swap(conv_tensors.xDesc, conv_tensors.yDesc); } - + HipEventProfiler pfr(handle); input1_tr_inst.ConvertFrom(handle, kernels, conv_tensors); input2_tr_inst.ConvertFrom(handle, kernels, conv_tensors); output_init_tr_inst.ConvertFrom(handle, kernels, conv_tensors); - /// \todo: Fix NHWC Wrw invokers to also issue a zero-out kernel. Will - /// need SetTensor() to properly zero out non-packed tensors + /// \todo: Will need SetTensor() to properly zero out non-packed tensors if(output_tr_inst.GetConvOperandTag() == internal::ConvOperandTag::Weights) { output_tr_inst.ZeroOutBuffer(); @@ -632,15 +658,7 @@ ConvSolution InitInvokerFactoryNCHW(const ExecutionContext& ctx, tr_ptrs[0]->GetBufferPtr(), tr_ptrs[1]->GetBufferPtr(), tr_ptrs[2]->GetBufferPtr()); - float conv_time = 0; - conv_time += invoker_ptr->Run(argument_ptr.get(), - {handle.GetStream(), handle.IsProfilingEnabled()}); - - if(handle.IsProfilingEnabled()) - { - handle.AccumKernelTime(conv_time); - } - + invoker_ptr->Run(argument_ptr.get(), {handle.GetStream(), false}); output_tr_inst.ConvertTo(handle, kernels, conv_tensors); }; }; diff --git a/src/include/miopen/solver_id.hpp b/src/include/miopen/solver_id.hpp index dbf086030e..c52dc020ac 100644 --- a/src/include/miopen/solver_id.hpp +++ b/src/include/miopen/solver_id.hpp @@ -54,7 +54,9 @@ enum class Primitive Pooling, Normalization, Reduce, - Cat + Cat, + Mha, + Softmax }; struct MIOPEN_EXPORT Id diff --git a/src/include/miopen/sqlite_db.hpp b/src/include/miopen/sqlite_db.hpp index a90bcfc7db..f3c4d089db 100644 --- a/src/include/miopen/sqlite_db.hpp +++ b/src/include/miopen/sqlite_db.hpp @@ -183,10 +183,10 @@ class SQLite Statement& operator=(const Statement&) = delete; int Step(const SQLite& sql); std::string ColumnText(int idx); - std::string ColumnBlob(int idx); + std::vector ColumnBlob(int idx); int64_t ColumnInt64(int idx); int BindText(int idx, const std::string& txt); - int BindBlob(int idx, const std::string& blob); + int BindBlob(int idx, const std::vector& blob); int BindInt64(int idx, int64_t); }; @@ -210,7 +210,7 @@ class SQLiteBase { protected: public: - SQLiteBase(const std::string& filename_, bool is_system_) + SQLiteBase(DbKinds, const std::string& filename_, bool is_system_) : filename(filename_), is_system(is_system_) { if(DisableUserDbFileIO && !is_system) @@ -273,8 +273,8 @@ class SQLiteBase else { MIOPEN_LOG(log_level, - "Unable to read system database file:" + filename_ + - " Performance may degrade"); + "Unable to read system database file:" + << filename_ << " Performance may degrade"); } } } @@ -397,7 +397,7 @@ class SQLitePerfDb : public SQLiteBase { public: static constexpr char const* MIOPEN_PERFDB_SCHEMA_VER = "1.1.0"; - SQLitePerfDb(const std::string& filename_, bool is_system); + SQLitePerfDb(DbKinds db_kind, const std::string& filename_, bool is_system); template inline void InsertConfig(const T& prob_desc) diff --git a/src/include/miopen/tensor.hpp b/src/include/miopen/tensor.hpp index 0291617891..437668cfcb 100644 --- a/src/include/miopen/tensor.hpp +++ b/src/include/miopen/tensor.hpp @@ -171,16 +171,25 @@ struct MIOPEN_EXPORT TensorDescriptor : miopenTensorDescriptor // Use only for external API static TensorDescriptor MakeDescriptor(miopenDataType_t t, const int* plens, int size); + static TensorDescriptor MakeDescriptor(miopenDataType_t t, const std::size_t* plens, int size); static TensorDescriptor MakeDescriptor(miopenDataType_t t, miopenTensorLayout_t layout, const int* plens, int size); + static TensorDescriptor MakeDescriptor(miopenDataType_t t, + miopenTensorLayout_t layout, + const std::size_t* plens, + int size); static TensorDescriptor MakeDescriptor(miopenDataType_t t, const int* plens, const int* pstrides, int size); + static TensorDescriptor MakeDescriptor(miopenDataType_t t, + const std::size_t* plens, + const std::size_t* pstrides, + int size); bool IsVectorized() const; const std::vector& GetLengths() const; const std::vector& GetStrides() const; - int GetSize() const; + unsigned GetNumDims() const; miopenDataType_t GetType() const; miopenTensorLayout_t GetLayout_t() const; @@ -205,7 +214,10 @@ struct MIOPEN_EXPORT TensorDescriptor : miopenTensorDescriptor } bool IsPacked() const; + /// Checks all lengths and strides. bool AllDimsFitIntoInt() const; + /// Checks only lengths. + bool AllLengthsFitIntoInt() const; bool operator==(const TensorDescriptor& rhs) const; bool operator!=(const TensorDescriptor& rhs) const; diff --git a/src/include/miopen/tensor_layout.hpp b/src/include/miopen/tensor_layout.hpp index 2b0920d798..f5659d7dd3 100644 --- a/src/include/miopen/tensor_layout.hpp +++ b/src/include/miopen/tensor_layout.hpp @@ -62,18 +62,21 @@ void tensor_layout_to_strides(const std::vector& len, } return std::accumulate(layout.begin() + pos + 1, layout.end(), - 1, + static_cast(1), [&dim_to_len](T accumulator, char l) { return accumulator * dim_to_len[l]; }); }); } +/// \brief Version for vectorized layouts. +/// +/// \todo Generalize with non-vectorized version, 90% of code is the same. template void tensor_layout_to_strides(const std::vector& len, const std::string& len_layout, const std::string& layout, - const int vector, + const std::size_t vector_size, std::vector& strides) { const std::string base_layout = layout.substr(0, len.size()); @@ -91,7 +94,7 @@ void tensor_layout_to_strides(const std::vector& len, len_layout.begin(), len_layout.end(), std::back_inserter(strides), - [&base_layout, &vector, &dim_to_len](char cur_layout_char) { + [&base_layout, &vector_size, &dim_to_len](char cur_layout_char) { auto pos = base_layout.find(cur_layout_char); if(pos == std::string::npos) { @@ -100,12 +103,12 @@ void tensor_layout_to_strides(const std::vector& len, return std::accumulate( base_layout.begin() + pos + 1, base_layout.end(), - vector, + vector_size, [&dim_to_len](T accumulator, char l) { return accumulator * dim_to_len[l]; }); }); } -inline std::string tensor_layout_get_default(int size) +inline std::string tensor_layout_get_default(unsigned size) { if(size == 4) return "NCHW"; diff --git a/src/include/miopen/tmp_dir.hpp b/src/include/miopen/tmp_dir.hpp index dfe516c5bb..a220b06230 100644 --- a/src/include/miopen/tmp_dir.hpp +++ b/src/include/miopen/tmp_dir.hpp @@ -9,15 +9,17 @@ namespace miopen { struct TmpDir { fs::path path; - TmpDir(std::string prefix); + explicit TmpDir(std::string_view prefix = ""); - TmpDir(TmpDir const&) = delete; - TmpDir& operator=(TmpDir const&) = delete; + TmpDir(TmpDir&&) = default; + TmpDir& operator=(TmpDir&&) = default; - TmpDir(TmpDir&& other) noexcept { (*this) = std::move(other); } - TmpDir& operator=(TmpDir&& other) noexcept; + fs::path operator/(std::string_view other) const { return path / other; } - void Execute(std::string_view exe, std::string_view args) const; + operator const fs::path&() const { return path; } + operator std::string() const { return path.string(); } + + int Execute(std::string_view cmd, std::string_view args) const; ~TmpDir(); }; diff --git a/src/include/miopen/utility/transposing_solver.hpp b/src/include/miopen/utility/transposing_solver.hpp index 41d7f9f328..99978a43be 100644 --- a/src/include/miopen/utility/transposing_solver.hpp +++ b/src/include/miopen/utility/transposing_solver.hpp @@ -292,7 +292,7 @@ struct ProblemTensorTransposeDescriptor inline TensorDescriptor Transpose(const TensorDescriptor& in) const { - const auto labels = tensor_layout_get_default(in.GetSize()); + const auto labels = tensor_layout_get_default(in.GetNumDims()); auto derived_strides = std::vector{}; tensor_layout_to_strides( in.GetLengths(), labels, SyncLayoutDims(labels.c_str(), to), derived_strides); @@ -441,7 +441,7 @@ struct TransposingSolver : Base for(auto transpose : Derived::GetTransposes()) { decltype(auto) descriptor = (problem.*(transpose.cdescriptor))(); - const auto labels = tensor_layout_get_default(descriptor.GetSize()); + const auto labels = tensor_layout_get_default(descriptor.GetNumDims()); const auto layout = descriptor.GetLayout(labels); const auto to = SyncLayoutDims(layout.c_str(), transpose.to); @@ -488,7 +488,7 @@ struct TransposingSolver : Base for(auto transpose : Derived::GetTransposes()) { const auto& descriptor = (problem.*(transpose.cdescriptor))(); - const auto labels = tensor_layout_get_default(descriptor.GetSize()); + const auto labels = tensor_layout_get_default(descriptor.GetNumDims()); const auto layout = descriptor.GetLayout(labels); const auto to = SyncLayoutDims(labels.c_str(), transpose.to); diff --git a/src/include/miopen/visit_float.hpp b/src/include/miopen/visit_float.hpp index b242988383..b8f877c3dd 100644 --- a/src/include/miopen/visit_float.hpp +++ b/src/include/miopen/visit_float.hpp @@ -28,11 +28,7 @@ #define GUARD_MLOPEN_VISIT_FLOAT_HPP #include -#if !defined(_WIN32) #include -#else -#include -#endif #include namespace miopen { diff --git a/src/include/miopen/write_file.hpp b/src/include/miopen/write_file.hpp index 718e75de36..8320aed3da 100644 --- a/src/include/miopen/write_file.hpp +++ b/src/include/miopen/write_file.hpp @@ -1,27 +1,48 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ + #ifndef GUARD_MLOPEN_WRITE_FILE_HPP #define GUARD_MLOPEN_WRITE_FILE_HPP #include -#include #include namespace miopen { -using FilePtr = MIOPEN_MANAGE_PTR(FILE*, std::fclose); - -inline void WriteFile(const std::string& content, const fs::path& name) +inline void WriteFile(std::string_view content, const fs::path& name) { - // std::cerr << "Write file: " << name << std::endl; - const FilePtr f{std::fopen(name.string().c_str(), "w")}; - if(std::fwrite(content.c_str(), 1, content.size(), f.get()) != content.size()) + std::ofstream f{name}; + if(f.write(content.data(), content.size()).fail()) MIOPEN_THROW("Failed to write to file"); } inline void WriteFile(const std::vector& content, const fs::path& name) { - // std::cerr << "Write file: " << name << std::endl; - const FilePtr f{std::fopen(name.string().c_str(), "w")}; - if(std::fwrite(&content[0], 1, content.size(), f.get()) != content.size()) + std::ofstream f{name, std::ios::binary}; + if(f.write(content.data(), content.size()).fail()) MIOPEN_THROW("Failed to write to file"); } diff --git a/src/kern_db.cpp b/src/kern_db.cpp index 9f16cdd388..4fb7aec98f 100644 --- a/src/kern_db.cpp +++ b/src/kern_db.cpp @@ -26,16 +26,20 @@ #include namespace miopen { -KernDb::KernDb(const std::string& filename_, bool is_system_) - : KernDb(filename_, is_system_, compress, decompress) +KernDb::KernDb(DbKinds db_kind, const std::string& filename_, bool is_system_) + : KernDb(db_kind, filename_, is_system_, compress, decompress) { } -KernDb::KernDb(const std::string& filename_, - bool is_system_, - std::function compress_fn_, - std::function decompress_fn_) - : SQLiteBase(filename_, is_system_), compress_fn(compress_fn_), decompress_fn(decompress_fn_) +KernDb::KernDb( + DbKinds db_kind, + const std::string& filename_, + bool is_system_, + std::function(const std::vector&, bool*)> compress_fn_, + std::function(const std::vector&, unsigned int)> decompress_fn_) + : SQLiteBase(db_kind, filename_, is_system_), + compress_fn(compress_fn_), + decompress_fn(decompress_fn_) { if(!is_system && DisableUserDbFileIO) return; diff --git a/src/kernels/MIOpenCheckNumerics.cl b/src/kernels/MIOpenCheckNumerics.cl deleted file mode 100644 index 693d50818d..0000000000 --- a/src/kernels/MIOpenCheckNumerics.cl +++ /dev/null @@ -1,171 +0,0 @@ -#include "float_types.h" - -#define DTYPE _FLOAT -#define ACCUMTYPE float - -// Must keep this structure synchronized with one in MIOpenCheckNumerics -struct CheckNumericsResult -{ - float sum; - float absSum; - float min; - float max; - - int hasZero; - int hasNan; - int hasInf; -}; - -union AtomicFloat -{ - unsigned int u32; - float f32; -}; - -void cl_atomic_add_float(volatile __global float* addr, float val) -{ - union AtomicFloat current, expected, next; - - current.f32 = *addr; - do - { - expected.f32 = current.f32; - next.f32 = current.f32 + val; - - current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32); - } while(current.u32 != expected.u32); -} - -void cl_atomic_min_float(volatile __global float* addr, float val) -{ - union AtomicFloat current, expected, next; - - current.f32 = *addr; - do - { - expected.f32 = current.f32; - next.f32 = fmin(current.f32, val); - - current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32); - } while(current.u32 != expected.u32); -} - -void cl_atomic_max_float(volatile __global float* addr, float val) -{ - union AtomicFloat current, expected, next; - - current.f32 = *addr; - do - { - expected.f32 = current.f32; - next.f32 = fmax(current.f32, val); - - current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32); - } while(current.u32 != expected.u32); -} - -#define GROUP_SIZE 256 -#define NUM_STATS 4 - -#define REDUCE_OPS(w) \ - if(lid < w) \ - { \ - stats[NUM_STATS * (lid) + 0] += stats[NUM_STATS * (lid + w) + 0]; \ - stats[NUM_STATS * (lid) + 1] += stats[NUM_STATS * (lid + w) + 1]; \ - stats[NUM_STATS * (lid) + 2] = \ - fmin(stats[NUM_STATS * (lid) + 2], stats[NUM_STATS * (lid + w) + 2]); \ - stats[NUM_STATS * (lid) + 3] = \ - fmax(stats[NUM_STATS * (lid) + 3], stats[NUM_STATS * (lid + w) + 3]); \ - barrier(CLK_LOCAL_MEM_FENCE); \ - } - -// Checks a block of data for abnormal numeric values : -__kernel void MIOpenCheckNumerics(const __global DTYPE* data, - int size, - __global struct CheckNumericsResult* abnormal, - int computeStats) -{ - const int lid = get_local_id(0); - const int gid = get_global_id(0); - const int total_wi_size = get_global_size(0); - - local float stats[4 * GROUP_SIZE]; - - int offset = gid; - ACCUMTYPE sum = 0.0f; - ACCUMTYPE abssum = 0.0f; - DTYPE minV = FLT_MAX; - DTYPE maxV = FLT_MIN; - while(offset < size) - { - DTYPE value = data[offset]; - sum += (ACCUMTYPE)value; - abssum += (ACCUMTYPE)(fabs( -#if MIOPEN_USE_BFP16 == 1 - bfloat16_to_float(value) -#else - value -#endif - )); - minV = min(minV, value); - maxV = max(maxV, value); - - if((ACCUMTYPE)(fabs( -#if MIOPEN_USE_BFP16 == 1 - bfloat16_to_float(value) -#else - value -#endif - )) <= 0.0f) - { // iszero check - abnormal->hasZero = 1; - } - if(isnan( -#if MIOPEN_USE_BFP16 == 1 - bfloat16_to_float(value) -#else - value -#endif - )) - { - abnormal->hasNan = 1; - } - if(isinf( -#if MIOPEN_USE_BFP16 == 1 - bfloat16_to_float(value) -#else - value -#endif - )) - { - abnormal->hasInf = 1; - } - offset += total_wi_size; - } - - if(computeStats) - { - stats[NUM_STATS * lid + 0] = (float)sum; - stats[NUM_STATS * lid + 1] = (float)abssum; - stats[NUM_STATS * lid + 2] = (float)minV; - stats[NUM_STATS * lid + 3] = (float)maxV; - barrier(CLK_LOCAL_MEM_FENCE); - - REDUCE_OPS(128) - REDUCE_OPS(64) - REDUCE_OPS(32) - REDUCE_OPS(16) - REDUCE_OPS(8) - REDUCE_OPS(4) - REDUCE_OPS(2) - REDUCE_OPS(1) - - if(lid == 0) - { - cl_atomic_add_float(&abnormal->sum, stats[0]); - cl_atomic_add_float(&abnormal->absSum, stats[1]); - cl_atomic_min_float(&abnormal->min, stats[2]); - cl_atomic_max_float(&abnormal->max, stats[3]); - } - } -} diff --git a/src/kernels/MIOpenRNNHiddenStateUpdate.cl b/src/kernels/MIOpenRNNHiddenStateUpdate.cl index 2e7a0928c6..3abdeee30d 100644 --- a/src/kernels/MIOpenRNNHiddenStateUpdate.cl +++ b/src/kernels/MIOpenRNNHiddenStateUpdate.cl @@ -110,6 +110,7 @@ __kernel void LSTMFwdHidUpdate(const global _FLOAT* cx, int rsv_idx = b_idx * hy_stride + h_idx; *((READ_TYPE*)s_dat) = *((const global READ_TYPE*)(reservespace + i_offset + rsv_idx)); + ActivationFunction_Sigmoid( RD_BLCK, i_dat, (const _FLOAT*)s_dat, activ_param, activ_param, activ_param); @@ -170,22 +171,26 @@ __kernel void LSTMFwdHidUpdate(const global _FLOAT* cx, } ActivationFunction_TanH(RD_BLCK, cx_dat, s_dat, activ_param, activ_param, activ_param); +#if !INFERENCE_MODE *((global READ_TYPE*)(reservespace + i_offset + rsv_idx)) = *((READ_TYPE*)i_dat); *((global READ_TYPE*)(reservespace + f_offset + rsv_idx)) = *((READ_TYPE*)f_dat); *((global READ_TYPE*)(reservespace + o_offset + rsv_idx)) = *((READ_TYPE*)o_dat); *((global READ_TYPE*)(reservespace + c_offset + rsv_idx)) = *((READ_TYPE*)c_dat); +#endif + + *((global READ_TYPE*)(reservespace + cell_offset + rsv_idx)) = *((READ_TYPE*)s_dat); // Ct - *((global READ_TYPE*)(reservespace + cell_offset + rsv_idx)) = *((READ_TYPE*)s_dat); #if !INFERENCE_MODE *((global READ_TYPE*)(reservespace + activ_cell_offset + b_idx * hy_stride / 6 + h_idx)) = *((READ_TYPE*)cx_dat); #endif + for(int i = 0; i < RD_BLCK; ++i) { s_dat[i] = o_dat[i] * cx_dat[i]; } - *((global READ_TYPE*)(reservespace + hidden_offset + rsv_idx)) = *((READ_TYPE*)s_dat); + *((global READ_TYPE*)(reservespace + hidden_offset + rsv_idx)) = *((READ_TYPE*)s_dat); // Ht } } #endif diff --git a/src/kernels/MIOpenSoftmax.cl b/src/kernels/MIOpenSoftmax.cl index 4d51471897..74667cc3d8 100644 --- a/src/kernels/MIOpenSoftmax.cl +++ b/src/kernels/MIOpenSoftmax.cl @@ -271,6 +271,7 @@ __kernel void SoftmaxForward(global _FLOAT* x, } _FLOAT channel_max = l_helper[0]; + barrier(CLK_LOCAL_MEM_FENCE); #else _FLOAT #endif @@ -508,6 +509,7 @@ __kernel void SoftmaxForward(global _FLOAT* x, } _FLOAT channel_max = l_helper[batch * BATCH_SIZE]; + barrier(CLK_LOCAL_MEM_FENCE); #else _FLOAT #endif diff --git a/src/kernels/MIOpenSoftmaxAttn.cpp b/src/kernels/MIOpenSoftmaxAttn.cpp index 6f64e63416..96c4b82204 100644 --- a/src/kernels/MIOpenSoftmaxAttn.cpp +++ b/src/kernels/MIOpenSoftmaxAttn.cpp @@ -23,37 +23,37 @@ * SOFTWARE. * *******************************************************************************/ + +#ifndef MIOPEN_DONT_USE_HIP_RUNTIME_HEADERS #include +#endif -#include "miopen_limits.hpp" #include "miopen_cstdint.hpp" +#include "miopen_limits.hpp" +#include "miopen_rocrand.hpp" #ifndef THREADS #define THREADS 64 #endif namespace { -template -constexpr DST bitcast(SRC val) +constexpr float plus_op(float a, float b) { return a + b; }; +constexpr float fmaxf_op(float a, float b) { return fmaxf(a, b); }; + +/// Atomically calculates maximum of non-negative ordered values. +/// Produces wrong results for negatve values or nans, +/// but it is a final amax reducton step and we expect only non-negative ordered values. +__forceinline__ __device__ float atomicMaxOfNonNegative(float* addr, float value) { - static_assert(sizeof(DST) == sizeof(SRC)); - DST tmp; - /// TODO: wait for C++20 and use std::bit_cast - /// until then it's the true way of type puning - __builtin_memcpy(&tmp, &val, sizeof(DST)); - /// unions can work for C99 and later but not for C++, - /// though compilers widely and unoficially support it - return tmp; + // ordered non-negatve and even infinity values can be compared as integers + // NOLINTBEGIN + // cppcheck-suppress invalidPointerCast + return __int_as_float(atomicMax(reinterpret_cast(addr), __float_as_int(value))); + // NOLINTEND } -constexpr float max_op(float a, float b) { return a > b ? a : b; }; -constexpr float max_abs_op(float a, float b) { return max_op(abs(a), abs(b)); }; -constexpr float plus_op(float a, float b) { return a + b; }; -constexpr float identety_op(float a) { return a; }; -} // namespace - template -__device__ float reductionFullWarp(float reduced_val, uint32_t laneId, Op op) +__forceinline__ __device__ float reductionFullWarp(float reduced_val, uint32_t laneId, Op op) { static_assert(WARP_SIZE != 0, "WARP_SIZEmust not be 0"); static_assert((SWIZZLE_SIZE & (SWIZZLE_SIZE - 1)) == 0, @@ -74,8 +74,8 @@ __device__ float reductionFullWarp(float reduced_val, uint32_t laneId, Op op) idx = idx >= ((laneId + WARP_SIZE) & ~warp_msk) ? laneId : idx; int itmp = - __builtin_amdgcn_ds_bpermute(static_cast(idx << 2), bitcast(reduced_val)); - tmp = bitcast(itmp); + __builtin_amdgcn_ds_bpermute(static_cast(idx << 2), __float_as_int(reduced_val)); + tmp = __int_as_float(itmp); } else { @@ -109,9 +109,11 @@ __device__ float reductionFullWarp(float reduced_val, uint32_t laneId, Op op) }; template -__device__ float +__forceinline__ __device__ float reductionBlock(float local_val, Op op, uint32_t lid, uint32_t laneId, uint32_t warpId) { + static_assert(NumWarps <= warpSize); + static_assert((NumWarps & (NumWarps - 1)) == 0, "NumWarps must be a power of 2"); __shared__ float reduction_tmp[NumWarps]; float reduced_val = reductionFullWarp(local_val, laneId, op); @@ -133,14 +135,14 @@ reductionBlock(float local_val, Op op, uint32_t lid, uint32_t laneId, uint32_t w }; template -__device__ float reductionCommon(const float* __restrict__ line, - const float init_value, - const uint32_t seq_len, - ReductionOp&& op, - ElementOp&& eop, - uint32_t lid, - uint32_t laneId, - uint32_t warpId) +__forceinline__ __device__ float reductionCommon(const float* __restrict__ line, + const float init_value, + const uint32_t seq_len, + ReductionOp&& op, + ElementOp&& eop, + uint32_t lid, + uint32_t laneId, + uint32_t warpId) { float reduced_val = (lid < seq_len) ? eop(line[lid]) : init_value; @@ -150,62 +152,43 @@ __device__ float reductionCommon(const float* __restrict__ line, return reductionBlock(reduced_val, op, lid, laneId, warpId); }; -extern "C" __global__ void MaxAbsReductionWarp(float* __restrict__ val, uint32_t len) -{ - const uint32_t lid = threadIdx.x; - const uint32_t laneId = threadIdx.x % warpSize; - - float local_val = (lid < len) ? (*val) : 0; - float r_max = reductionFullWarp(local_val, laneId, max_abs_op); - - if(lid == 0) - val[0] = r_max; -} - -extern "C" __global__ void MaxAbsReductionBlock(float* __restrict__ val, uint32_t len) +__forceinline__ __device__ bool doDropout(float dropout, rocrand_device::xorwow_engine* state) { - constexpr uint32_t NumWarps = THREADS / warpSize; - const uint32_t lid = threadIdx.x; - const uint32_t laneId = threadIdx.x % warpSize; - const uint32_t warpId = threadIdx.x / warpSize; - - float local_val = (lid < len) ? (*val) : 0; - float r_max = reductionBlock(local_val, max_abs_op, lid, laneId, warpId); - - if(lid == 0) - val[0] = r_max; + return (dropout > 0.0f && prng::xorwow_uniform(state) < dropout); } +} // namespace -extern "C" __global__ void MaxAbsReductionCommon(float* __restrict__ val, uint32_t len) +extern "C" __global__ void __launch_bounds__(THREADS) + SoftMaxWarp(const float* in, + float* out, + float* __restrict__ M, + float* __restrict__ Z, + float* __restrict__ Amax, + const float* __restrict__ descale_Q, + const float* __restrict__ descale_K, + const float* __restrict__ scale_S, + const uint64_t* __restrict__ seed, + const uint64_t* __restrict__ offset, + const float* __restrict__ dropout_P, + uint32_t seq_len, + uint64_t nhs) { + static_assert(THREADS % warpSize == 0); constexpr uint32_t NumWarps = THREADS / warpSize; const uint32_t lid = threadIdx.x; - const uint32_t laneId = threadIdx.x % warpSize; - const uint32_t warpId = threadIdx.x / warpSize; - - float r_max = - reductionCommon(val, 0, len, max_abs_op, identety_op, lid, laneId, warpId); - - if(lid == 0) - val[0] = r_max; -} + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + const float descaler = (descale_Q ? *descale_Q : 1.0f) * (descale_K ? *descale_K : 1.0f); + const float dropout = (dropout_P && seed && offset) ? (*dropout_P) : 0.0f; + const float scaler = (scale_S ? *scale_S : 1.0f) / (1.0f - dropout); + const bool save_stats = M && Z && (laneId == 0); -extern "C" __global__ void SoftMaxWarp(const float* in, - float* out, - float* __restrict__ M, - float* __restrict__ Z, - float* __restrict__ AmaxWorkspace, - const float* __restrict__ descale_Q, - const float* __restrict__ descale_K, - uint32_t seq_len, - uint64_t nhs) -{ - const uint32_t NumWarps = THREADS / warpSize; - const uint32_t lid = threadIdx.x; - const uint32_t laneId = lid % warpSize; - const uint32_t warpId = lid / warpSize; - const float descaler = (descale_Q ? *descale_Q : 1.f) * (descale_K ? *descale_K : 1.f); - const bool save_stats = M && Z && laneId == 0; + rocrand_state_xorwow rng; + if(dropout > 0.0f) + { + const uint64_t idx = blockIdx.x * blockDim.x + threadIdx.x; + rocrand_init(prng::hash(*seed + idx), 0, *offset, &rng); + } float r_Amax = 0; @@ -217,19 +200,24 @@ extern "C" __global__ void SoftMaxWarp(const float* in, float local_val = (laneId < seq_len) ? (*line) * descaler : std::numeric_limits::lowest(); - float r_max = reductionFullWarp(local_val, laneId, max_op); + float r_max = reductionFullWarp(local_val, laneId, fmaxf_op); - local_val = (laneId < seq_len) ? exp(local_val - r_max) : 0; + local_val = (laneId < seq_len) ? expf(local_val - r_max) : 0; - float r_sum = 1.f / reductionFullWarp(local_val, laneId, plus_op); + float r_sum = 1.0f / reductionFullWarp(local_val, laneId, plus_op); + + local_val *= r_sum; + + // It is supposed to be maximum of absolute values, + // however we do not need abs() because expf() above produces + // non-negative value. Plain max() is enough. + r_Amax = fmaxf_op(r_Amax, local_val); - local_val = (laneId < seq_len) ? local_val * r_sum : 0; if(laneId < seq_len) { - *res = local_val; + *res = doDropout(dropout, &rng) ? 0.0f : local_val * scaler; } - r_Amax = max_abs_op(r_Amax, local_val); if(save_stats) { M[gid] = r_max; @@ -237,32 +225,47 @@ extern "C" __global__ void SoftMaxWarp(const float* in, } } - if(AmaxWorkspace) + if(Amax) { - r_Amax = reductionBlock(r_Amax, max_abs_op, lid, laneId, warpId); + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); if(lid == 0) { - AmaxWorkspace[blockIdx.x] = r_Amax; + atomicMaxOfNonNegative(Amax, r_Amax); } } } -extern "C" __global__ void SoftMaxBlock(const float* in, - float* out, - float* __restrict__ M, - float* __restrict__ Z, - float* __restrict__ AmaxWorkspace, - const float* __restrict__ descale_Q, - const float* __restrict__ descale_K, - uint32_t seq_len, - uint64_t nhs) +extern "C" __global__ void __launch_bounds__(THREADS) + SoftMaxBlock(const float* in, + float* out, + float* __restrict__ M, + float* __restrict__ Z, + float* __restrict__ Amax, + const float* __restrict__ descale_Q, + const float* __restrict__ descale_K, + const float* __restrict__ scale_S, + const uint64_t* __restrict__ seed, + const uint64_t* __restrict__ offset, + const float* __restrict__ dropout_P, + uint32_t seq_len, + uint64_t nhs) { + static_assert(THREADS % warpSize == 0); constexpr uint32_t NumWarps = THREADS / warpSize; const uint32_t lid = threadIdx.x; const uint32_t laneId = lid % warpSize; const uint32_t warpId = lid / warpSize; - const float descaler = (descale_Q ? *descale_Q : 1.f) * (descale_K ? *descale_K : 1.f); - const bool save_stats = M && Z && lid == 0; + const float descaler = (descale_Q ? *descale_Q : 1.0f) * (descale_K ? *descale_K : 1.0f); + const float dropout = (dropout_P && seed && offset) ? (*dropout_P) : 0.0f; + const float scaler = (scale_S ? *scale_S : 1.0f) / (1.0f - dropout); + const bool save_stats = M && Z && (lid == 0); + + rocrand_state_xorwow rng; + if(dropout > 0.0f) + { + const uint64_t idx = blockIdx.x * blockDim.x + threadIdx.x; + rocrand_init(prng::hash(*seed + idx), 0, *offset, &rng); + } float r_Amax = 0; @@ -273,20 +276,24 @@ extern "C" __global__ void SoftMaxBlock(const float* in, float local_val = (lid < seq_len) ? (*line) * descaler : std::numeric_limits::lowest(); - float r_max = reductionBlock(local_val, max_op, lid, laneId, warpId); + float r_max = reductionBlock(local_val, fmaxf_op, lid, laneId, warpId); + + local_val = (lid < seq_len) ? expf(local_val - r_max) : 0; - local_val = (lid < seq_len) ? exp(local_val - r_max) : 0; + float r_sum = 1.0f / reductionBlock(local_val, plus_op, lid, laneId, warpId); - float r_sum = 1.f / reductionBlock(local_val, plus_op, lid, laneId, warpId); + local_val *= r_sum; + + // It is supposed to be maximum of absolute values, + // however we do not need abs() because expf() above produces + // non-negative value. Plain max() is enough. + r_Amax = fmaxf_op(r_Amax, local_val); - local_val = (lid < seq_len) ? local_val * r_sum : 0; if(lid < seq_len) { - *res = local_val; + *res = doDropout(dropout, &rng) ? 0.0f : local_val * scaler; } - r_Amax = max_abs_op(r_Amax, local_val); - if(save_stats) { M[gid] = r_max; @@ -294,32 +301,47 @@ extern "C" __global__ void SoftMaxBlock(const float* in, } } - if(AmaxWorkspace) + if(Amax) { - r_Amax = reductionBlock(r_Amax, max_abs_op, lid, laneId, warpId); + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); if(lid == 0) { - AmaxWorkspace[blockIdx.x] = r_Amax; + atomicMaxOfNonNegative(Amax, r_Amax); } } } -extern "C" __global__ void SoftMaxCommon(const float* in, - float* out, - float* __restrict__ M, - float* __restrict__ Z, - float* __restrict__ AmaxWorkspace, - const float* __restrict__ descale_Q, - const float* __restrict__ descale_K, - uint32_t seq_len, - uint64_t nhs) +extern "C" __global__ void __launch_bounds__(THREADS) + SoftMaxCommon(const float* in, + float* out, + float* __restrict__ M, + float* __restrict__ Z, + float* __restrict__ Amax, + const float* __restrict__ descale_Q, + const float* __restrict__ descale_K, + const float* __restrict__ scale_S, + const uint64_t* __restrict__ seed, + const uint64_t* __restrict__ offset, + const float* __restrict__ dropout_P, + uint32_t seq_len, + uint64_t nhs) { + static_assert(THREADS % warpSize == 0); constexpr uint32_t NumWarps = THREADS / warpSize; const uint32_t lid = threadIdx.x; const uint32_t laneId = lid % warpSize; const uint32_t warpId = lid / warpSize; - const float descaler = (descale_Q ? *descale_Q : 1.f) * (descale_K ? *descale_K : 1.f); - const bool save_stats = M && Z && lid == 0; + const float descaler = (descale_Q ? *descale_Q : 1.0f) * (descale_K ? *descale_K : 1.0f); + const float dropout = (dropout_P && seed && offset) ? (*dropout_P) : 0.0f; + const float scaler = (scale_S ? *scale_S : 1.0f) / (1.0f - dropout); + const bool save_stats = M && Z && (lid == 0); + + rocrand_state_xorwow rng; + if(dropout > 0.0f) + { + const uint64_t idx = blockIdx.x * blockDim.x + threadIdx.x; + rocrand_init(prng::hash(*seed + idx), 0, *offset, &rng); + } float r_Amax = 0; @@ -332,27 +354,32 @@ extern "C" __global__ void SoftMaxCommon(const float* in, line, std::numeric_limits::lowest(), seq_len, - max_op, + fmaxf_op, [descaler](float x) { return x * descaler; }, lid, laneId, warpId); - float r_sum = 1.f / reductionCommon( - line, - 0, - seq_len, - plus_op, - [r_max](float x) { return exp(x - r_max); }, - lid, - laneId, - warpId); + float r_sum = 1.0f / reductionCommon( + line, + 0, + seq_len, + plus_op, + [r_max, descaler](float x) { return expf(x * descaler - r_max); }, + lid, + laneId, + warpId); for(uint32_t loop_lid = lid; loop_lid < seq_len; loop_lid += blockDim.x) { - float local_val = exp(line[loop_lid] - r_max) * r_sum; - res[loop_lid] = local_val; - r_Amax = max_abs_op(r_Amax, local_val); + float local_val = expf(line[loop_lid] * descaler - r_max) * r_sum; + + // It is supposed to be maximum of absolute values, + // however we do not need abs() because expf() above produces + // non-negative value. Plain max() is enough. + r_Amax = fmaxf_op(r_Amax, local_val); + + res[loop_lid] = doDropout(dropout, &rng) ? 0.0f : local_val * scaler; } if(save_stats) @@ -362,12 +389,393 @@ extern "C" __global__ void SoftMaxCommon(const float* in, } } - if(AmaxWorkspace) + if(Amax) + { + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); + if(lid == 0) + { + atomicMaxOfNonNegative(Amax, r_Amax); + } + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + ScaleReduce(const float* __restrict__ in, + float* __restrict__ out, + float* __restrict__ Amax, + const float* __restrict__ descale_S, + const float* __restrict__ descale_V, + const float* __restrict__ scale_O, + uint64_t nhsd) +{ + const float descaler = (*descale_S) * (*descale_V); + const float scaler = (*scale_O); + + const auto gid = blockIdx.x * blockDim.x + threadIdx.x; + const auto step = gridDim.x * blockDim.x; + + auto in_ptr = in + gid; + auto out_ptr = out + gid; + const auto end = in + nhsd; + + float r_Amax = 0; + + while(in_ptr < end) + { + const auto res = *in_ptr * descaler; + + r_Amax = fmaxf_op(r_Amax, fabsf(res)); + + *out_ptr = res * scaler; + + in_ptr += step; + out_ptr += step; + } + + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); + if(lid == 0) + { + atomicMaxOfNonNegative(Amax, r_Amax); + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + ScaleRowReduceWarp(const float* __restrict__ dO, + const float* __restrict__ O, + float* __restrict__ out, + const float* __restrict__ descale_dO, + const float* __restrict__ descale_O, + const float* __restrict__ dropout_P, + uint32_t d, + uint64_t nhs) +{ + static_assert(THREADS % warpSize == 0); + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + const float scaler = (*descale_dO) * (*descale_O) * (1.0f - (*dropout_P)); + + for(uint64_t gid = blockIdx.x * NumWarps + warpId; gid < nhs; gid += gridDim.x * NumWarps) + { + float local_val = 0.0f; + if(laneId < d) + { + const float* dO_ptr = dO + gid * d + laneId; + const float* O_ptr = O + gid * d + laneId; + + local_val = (*dO_ptr) * (*O_ptr) * scaler; + } + + local_val = reductionFullWarp(local_val, laneId, plus_op); + + if(laneId == 0) + { + out[gid] = local_val; + } + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + ScaleRowReduceBlock(const float* __restrict__ dO, + const float* __restrict__ O, + float* __restrict__ out, + const float* __restrict__ descale_dO, + const float* __restrict__ descale_O, + const float* __restrict__ dropout_P, + uint32_t d, + uint64_t nhs) +{ + static_assert(THREADS % warpSize == 0); + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + const float scaler = (*descale_dO) * (*descale_O) * (1.0f - (*dropout_P)); + + for(uint64_t gid = blockIdx.x; gid < nhs; gid += gridDim.x) + { + const float* dO_ptr = dO + gid * d + lid; + const float* O_ptr = O + gid * d + lid; + + float local_val = 0.0f; + if(lid < d) + { + local_val = (*dO_ptr) * (*O_ptr) * scaler; + } + + local_val = reductionBlock(local_val, plus_op, lid, laneId, warpId); + + if(lid == 0) + { + out[gid] = local_val; + } + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + ScaleRowReduceCommon(const float* __restrict__ dO, + const float* __restrict__ O, + float* __restrict__ out, + const float* __restrict__ descale_dO, + const float* __restrict__ descale_O, + const float* __restrict__ dropout_P, + uint32_t d, + uint64_t nhs) +{ + static_assert(THREADS % warpSize == 0); + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + const float scaler = (*descale_dO) * (*descale_O) * (1.0f - (*dropout_P)); + + for(uint64_t gid = blockIdx.x; gid < nhs; gid += gridDim.x) { - r_Amax = reductionBlock(r_Amax, max_abs_op, lid, laneId, warpId); + const float* dO_ptr = dO + gid * d; + const float* O_ptr = O + gid * d; + + float local_val = (lid < d) ? dO_ptr[lid] * O_ptr[lid] * scaler : 0.0f; + + for(uint32_t loop_lid = lid + blockDim.x; loop_lid < d; loop_lid += blockDim.x) + local_val += dO_ptr[loop_lid] * O_ptr[loop_lid] * scaler; + + local_val = reductionBlock(local_val, plus_op, lid, laneId, warpId); + if(lid == 0) { - AmaxWorkspace[blockIdx.x] = r_Amax; + out[gid] = local_val; + } + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + BwdAttentionWarp(float* __restrict__ QxK_S, + float* __restrict__ dOxV_dS, + const float* __restrict__ M, + const float* __restrict__ Zinv, + const float* __restrict__ dOxO, + float* __restrict__ Amax, + const float* __restrict__ descale_Q, + const float* __restrict__ descale_K, + const float* __restrict__ descale_dO, + const float* __restrict__ descale_V, + const float* __restrict__ scale_S, + const float* __restrict__ scale_dS, + const uint64_t* __restrict__ seed, + const uint64_t* __restrict__ offset, + const float* __restrict__ dropout_P, + float scale, + uint32_t seq_len, + uint64_t nhs) +{ + static_assert(THREADS % warpSize == 0); + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + + const float dropout = (dropout_P && seed && offset) ? (*dropout_P) : 0.0f; + const float scaler_dropout = 1.0f - dropout; + const float scaler_inv_dropout = 1.0f / scaler_dropout; + + const float descaler_QxK = (*descale_Q) * (*descale_K); + const float descaler_dOxV = (*descale_dO) * (*descale_V) * scaler_dropout; + + const float scaler_S = (*scale_S); + const float scaler_dS = (*scale_dS); + + rocrand_state_xorwow rng; + if(dropout > 0.0f) + { + const uint64_t idx = blockIdx.x * blockDim.x + threadIdx.x; + rocrand_init(prng::hash(*seed + idx), 0, *offset, &rng); + } + + float r_Amax = 0; + + for(uint64_t gid = blockIdx.x * NumWarps + warpId; gid < nhs && laneId < seq_len; + gid += gridDim.x * NumWarps) + { + const float M_val = M[gid]; + const float Zinv_val = Zinv[gid]; + const float dOxO_val = dOxO[gid]; + + const size_t idx = gid * seq_len + laneId; + + const float QxK_val = doDropout(dropout, &rng) ? 0.0f + : expf(QxK_S[idx] * descaler_QxK - M_val) * + Zinv_val * scaler_inv_dropout; + + QxK_S[idx] = QxK_val * scaler_S; + + const float dOxV_val = (dOxV_dS[idx] * descaler_dOxV - dOxO_val) * scale * QxK_val; + + dOxV_dS[idx] = dOxV_val * scaler_dS; + + r_Amax = fmaxf_op(r_Amax, fabsf(dOxV_val)); + } + + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); + if(lid == 0) + { + atomicMaxOfNonNegative(Amax, r_Amax); + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + BwdAttentionBlock(float* __restrict__ QxK_S, + float* __restrict__ dOxV_dS, + const float* __restrict__ M, + const float* __restrict__ Zinv, + const float* __restrict__ dOxO, + float* __restrict__ Amax, + const float* __restrict__ descale_Q, + const float* __restrict__ descale_K, + const float* __restrict__ descale_dO, + const float* __restrict__ descale_V, + const float* __restrict__ scale_S, + const float* __restrict__ scale_dS, + const uint64_t* __restrict__ seed, + const uint64_t* __restrict__ offset, + const float* __restrict__ dropout_P, + float scale, + uint32_t seq_len, + uint64_t nhs) +{ + static_assert(THREADS % warpSize == 0); + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + + const float dropout = (dropout_P && seed && offset) ? (*dropout_P) : 0.0f; + const float scaler_dropout = 1.0f - dropout; + const float scaler_inv_dropout = 1.0f / scaler_dropout; + + const float descaler_QxK = (*descale_Q) * (*descale_K); + const float descaler_dOxV = (*descale_dO) * (*descale_V) * scaler_dropout; + + const float scaler_S = (*scale_S); + const float scaler_dS = (*scale_dS); + + rocrand_state_xorwow rng; + if(dropout > 0.0f) + { + const uint64_t idx = blockIdx.x * blockDim.x + threadIdx.x; + rocrand_init(prng::hash(*seed + idx), 0, *offset, &rng); + } + + float r_Amax = 0; + + for(uint64_t gid = blockIdx.x; gid < nhs && lid < seq_len; gid += gridDim.x) + { + const float M_val = M[gid]; + const float Zinv_val = Zinv[gid]; + const float dOxO_val = dOxO[gid]; + + const size_t idx = gid * seq_len + lid; + + const float QxK_val = doDropout(dropout, &rng) ? 0.0f + : expf(QxK_S[idx] * descaler_QxK - M_val) * + Zinv_val * scaler_inv_dropout; + + QxK_S[idx] = QxK_val * scaler_S; + + const float dOxV_val = (dOxV_dS[idx] * descaler_dOxV - dOxO_val) * scale * QxK_val; + + dOxV_dS[idx] = dOxV_val * scaler_dS; + + r_Amax = fmaxf_op(r_Amax, fabsf(dOxV_val)); + } + + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); + if(lid == 0) + { + atomicMaxOfNonNegative(Amax, r_Amax); + } +} + +extern "C" __global__ void __launch_bounds__(THREADS) + BwdAttentionCommon(float* __restrict__ QxK_S, + float* __restrict__ dOxV_dS, + const float* __restrict__ M, + const float* __restrict__ Zinv, + const float* __restrict__ dOxO, + float* __restrict__ Amax, + const float* __restrict__ descale_Q, + const float* __restrict__ descale_K, + const float* __restrict__ descale_dO, + const float* __restrict__ descale_V, + const float* __restrict__ scale_S, + const float* __restrict__ scale_dS, + const uint64_t* __restrict__ seed, + const uint64_t* __restrict__ offset, + const float* __restrict__ dropout_P, + float scale, + uint32_t seq_len, + uint64_t nhs) +{ + static_assert(THREADS % warpSize == 0); + constexpr uint32_t NumWarps = THREADS / warpSize; + const uint32_t lid = threadIdx.x; + const uint32_t laneId = lid % warpSize; + const uint32_t warpId = lid / warpSize; + + const float dropout = (dropout_P && seed && offset) ? (*dropout_P) : 0.0f; + const float scaler_dropout = 1.0f - dropout; + const float scaler_inv_dropout = 1.0f / scaler_dropout; + + const float descaler_QxK = (*descale_Q) * (*descale_K); + const float descaler_dOxV = (*descale_dO) * (*descale_V) * scaler_dropout; + + const float scaler_S = (*scale_S); + const float scaler_dS = (*scale_dS); + + rocrand_state_xorwow rng; + if(dropout > 0.0f) + { + const uint64_t idx = blockIdx.x * blockDim.x + threadIdx.x; + rocrand_init(prng::hash(*seed + idx), 0, *offset, &rng); + } + + float r_Amax = 0; + + for(uint64_t gid = blockIdx.x; gid < nhs; gid += gridDim.x) + { + const float M_val = M[gid]; + const float Zinv_val = Zinv[gid]; + const float dOxO_val = dOxO[gid]; + + float* QxK_S_ptr = QxK_S + gid * seq_len; + float* dOxV_dS_ptr = dOxV_dS + gid * seq_len; + + for(uint32_t loop_lid = lid; loop_lid < seq_len; loop_lid += blockDim.x) + { + const float QxK_val = doDropout(dropout, &rng) + ? 0.0f + : expf(QxK_S_ptr[loop_lid] * descaler_QxK - M_val) * + Zinv_val * scaler_inv_dropout; + + QxK_S_ptr[loop_lid] = QxK_val * scaler_S; + + const float dOxV_val = + (dOxV_dS_ptr[loop_lid] * descaler_dOxV - dOxO_val) * scale * QxK_val; + + dOxV_dS_ptr[loop_lid] = dOxV_val * scaler_dS; + + r_Amax = fmaxf_op(r_Amax, fabsf(dOxV_val)); } } + + r_Amax = reductionBlock(r_Amax, fmaxf_op, lid, laneId, warpId); + if(lid == 0) + { + atomicMaxOfNonNegative(Amax, r_Amax); + } } diff --git a/src/kernels/gfx908_ConvAsm1x1U_metadata.ktn.model b/src/kernels/gfx908_ConvAsm1x1U_metadata.ktn.model index 8ebbaf9798..1032822814 100644 --- a/src/kernels/gfx908_ConvAsm1x1U_metadata.ktn.model +++ b/src/kernels/gfx908_ConvAsm1x1U_metadata.ktn.model @@ -1,5 +1,8 @@ { - "num_tuning_params": 8, + "num_tuning_params": { + "fwd": 8, + "bwd": 8 + }, "decodings": { "tunings": { "0": "-1", diff --git a/src/kernels/gfx90a.kdb.bz2 b/src/kernels/gfx90a.kdb.bz2 index 54dd0603cd..a53fb807e3 100644 --- a/src/kernels/gfx90a.kdb.bz2 +++ b/src/kernels/gfx90a.kdb.bz2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6d95688fb9229155290b1e50f36b500e27627e21a632e0a65c43cb27875531f -size 228961676 +oid sha256:d3febff109a05c5a9cac5e3db14d052081f8a15a431270613a27fba741a33947 +size 138714110 diff --git a/src/kernels/gfx90a68.HIP.fdb.txt.bz2 b/src/kernels/gfx90a68.HIP.fdb.txt.bz2 index 338b763363..0501fb90e7 100644 Binary files a/src/kernels/gfx90a68.HIP.fdb.txt.bz2 and b/src/kernels/gfx90a68.HIP.fdb.txt.bz2 differ diff --git a/src/kernels/gfx90a68.db.bz2 b/src/kernels/gfx90a68.db.bz2 index e48177b426..fb99472403 100644 Binary files a/src/kernels/gfx90a68.db.bz2 and b/src/kernels/gfx90a68.db.bz2 differ diff --git a/src/kernels/gfx90a_ConvHipIgemmGroupFwdXdlops_metadata.ktn.model b/src/kernels/gfx90a_ConvHipIgemmGroupFwdXdlops_metadata.ktn.model index fc95cc39eb..8641b3d450 100644 --- a/src/kernels/gfx90a_ConvHipIgemmGroupFwdXdlops_metadata.ktn.model +++ b/src/kernels/gfx90a_ConvHipIgemmGroupFwdXdlops_metadata.ktn.model @@ -1,5 +1,7 @@ { - "num_tuning_params": 9, + "num_tuning_params": { + "fwd": 9 + }, "decodings": { "tunings": { "0": "-1", diff --git a/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_decoder.ktn.model b/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_decoder.ktn.model new file mode 100644 index 0000000000..1a1a752e9b --- /dev/null +++ b/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_decoder.ktn.model @@ -0,0 +1 @@ +{"architecture":{"class_name":"Functional","config":{"name":"model_1","trainable":true,"layers":[{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,1],"dtype":"float32","sparse":false,"ragged":false,"name":"input_2"},"registered_name":null,"name":"input_2","inbound_nodes":[]},{"module":"keras.layers","class_name":"Embedding","config":{"name":"embedding","trainable":true,"dtype":"float32","batch_input_shape":[null,1],"input_dim":60,"output_dim":16,"embeddings_initializer":{"module":"keras.initializers","class_name":"RandomUniform","config":{"minval":-0.05,"maxval":0.05,"seed":null},"registered_name":null},"embeddings_regularizer":null,"activity_regularizer":null,"embeddings_constraint":null,"mask_zero":false,"input_length":1},"registered_name":null,"build_config":{"input_shape":[null,1]},"name":"embedding","inbound_nodes":[[["input_2",0,0,{}]]]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_3"},"registered_name":null,"name":"input_3","inbound_nodes":[]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_4"},"registered_name":null,"name":"input_4","inbound_nodes":[]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm_2","trainable":true,"dtype":"float32","return_sequences":true,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[[null,1,16],[null,64],[null,64]]},"name":"lstm_2","inbound_nodes":[[["embedding",0,0,{}],["input_3",0,0,{}],["input_4",0,0,{}]]]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_5"},"registered_name":null,"name":"input_5","inbound_nodes":[]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_6"},"registered_name":null,"name":"input_6","inbound_nodes":[]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm_3","trainable":true,"dtype":"float32","return_sequences":false,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[[null,1,64],[null,64],[null,64]]},"name":"lstm_3","inbound_nodes":[[["lstm_2",0,0,{}],["input_5",0,0,{}],["input_6",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_1","trainable":true,"dtype":"float32","units":60,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"dense_1","inbound_nodes":[[["lstm_3",0,0,{}]]]}],"input_layers":[["input_2",0,0],["input_3",0,0],["input_4",0,0],["input_5",0,0],["input_6",0,0]],"output_layers":[["dense_1",0,0],["lstm_2",0,1],["lstm_2",0,2],["lstm_3",0,1],["lstm_3",0,2]]},"keras_version":"2.16.0","backend":"tensorflow"},"image_data_format":"channels_last","input_shapes":[[1],[64],[64],[64],[64]],"output_shapes":[[60],[64],[64],[64],[64]],"tests":[{"inputs":[{"shape":[1],"values":["AAAsQg=="]},{"shape":[64],"values":["xO0mP+qrwD7ufAq/8BLnPqZq7L8gPWm/AjYCQP3S/LwMVIy/VFSPvyL+0L9WVDc/lgs+vw8yTD+59Sq/JuGGP36bH78W2Ha/aZ6gvlwyOz81u7K/Q6FGv0k9Rj/Whoa/DkFEQPrXij9BIz0/vHoTP7ZlnD+KxR0/r0IVQFnTmz+9Keg+XZWPPozBxb61HJy/MGjSvk7CS7+bQ7k++80OP+xXoD8VXCM+voUovR1qOj5GDCC/70IZPmSGkb3r3y6/RF5uPyczKj4HjsS77dbnPryagryDh7I/gPAEv61sJL+PaTe+CoOFPm5lhj71igi//2oNPwFBiL+nDSe/jJkjvw=="]},{"shape":[64],"values":["yt2xvuLt9Dx4wx++MIevPudz/j+gWfC+Qoz0Pk47+j8dYsi/+AQyv28XPL8aIte9dDmUvzxIbb9gf9M/rjCFPx+gjT1rJTa/YXKwv+MtcrutW36/Mcxzv9dWIEAcoIW+LBiLvWeBcbzOa0g/AcIbPic94D1f4qY+SzdfvtxTjL+kBg8/yfyTvsBrJ7+pehe/O0+Avo37kL++1NQ+AyluP6xncb+zL3M/G1OTP0m+Mj+H//e/eLclQK1UaL/29bU95W3Iv0jNy78xJMg/6UcYPxs4qT8C6y++OSueP2ljDT/VqHK+apENPxEurD92ZLS+P88gPzBFbz+TbE6/mHHtPw=="]},{"shape":[64],"values":["KzI9vvz5/j9NOFe/CfZwvY8AOL8NPCW/urvWPlCVID+TXpa+n/CIv1SDQT7/dHS/JRqEvrH76D8WPsO/zW6iPsXtkL+NZZu/WTD2PyKg8r6dBFBAkM5pvsufwz8ap1c/VI7RPzZh5r6GmAPA22/4Pne1Ib73TKa/qWvQPLkfbL6e0NW/eazdvhw2Ib7xpaI/UiwJQNU5FD8w75++lOi/P6mFOb/38CC+ZiAvP+tNtb/tMd8+JdQcv6BwiT6Kifi+GrLtvzWGw75xufG/n8rRP4XqGz7BHUi/LkrYv+81C0Aqv4I/39IPP54Eh79qcsS+rX6RvgLIYr8vaS2/WEY3PQ=="]},{"shape":[64],"values":["zo6iP6Z70r7hdQNAloGHv9GCmL8wo+w+29TYPkU3Yb+TLFe/0otRv220f7/M4jM/4PHOv0lN9j5UXYI/OwXMPxxDbj+xT5C/lRyAPo3Ybz9mR5i/lpZmPwEgJD9vum4/BOl2vr7J1T65dq2/03imvw9ziT9Jcfm+KbGyvkOlUD8j/iq/X4eiPTse2D5NfXU/v/djP3UvwD025DC+5yXqv+mDoj/emw6/vue7P11L8b79PEi/VDOKP7cOuT6f340/ZFX4vfpUW7/skdW+8vMaP2YD4L8jfY0/xUVMv7UVuD+k85M/SuSZPmvI9D0yXkO/kEvpPxotsT2LKsu/WBPQPg=="]}],"outputs":[{"shape":[60],"values":["MHXxvRGL5b7qPb2/fM7Kv9Y2KD9PmklAxWYqwL5IEb8oYeW/dw0VwKS+Ob827zjA35ciQOBWYD3uvaW+OIveP/m97T/eknI/ouTnPjI6hb6o1EXACEtBvl3oZ8DBXY3AlBvzPntqPz76cddAglT6P0dqJ0D8NK4/77dgwF1mjsAsPa2+cmf+v4SPsb90k5FAj90RQGyMVL5sBlq/snSgwCGO57+VYzFABmaGv3Gtqb8KQ7g/ketgPzkw3j1ntAZAscIvQE2Jz78rig4/05jyv+vu3L/KshO/wg9RvzAFrj4oBABAdadavy0yxT/ay5VA"]},{"shape":[64],"values":["3Uuhvhcr771pvEq+MiPWPpHOZD2/owg/f4HIvSnX7j5AxSq+lBDAvqTSFzxrku2+0xIYv3gCPbiVkzA/9bwUPlBbz74jfe2+fqkmvgkTPr3F5OC7AOEOvkopeTzSps69ZWpXPK6wqD0dAus8zqYEvMsQ+75OH9g+lwEKOGlDAr8dC5++mNwauVfkJb/ODDe/o6Sbvg8VHr8nMyI/VyI5PkLfl7wQxyE/oYgzPzHfHz8mfqc+vywgPpMADb9Ptoc8Uawpv0MLEL1uPbI4DhefO8QOjzvOyNC9VvzDvmWoEj+7AEq+X4W1OzyJVb2WO9a7afNsPmnlJD6W0n+++K4VPw=="]},{"shape":[64],"values":["WvXuvsHmkb5mxFG+1LQgPxVGDj8njEI/YGzEvuKHIT/Gpa2/mwz6vvABWjzEF4i/uncvvwCYorgPoLk/VptAPgc63b42sgC/qNtjvyeQKr6cp1K/IeD6vjOKpj/7y9G96iGQPaJ00j6zk4E/vLfhvlJEDb86swo/6s8XPkyuJL8GQ9a+skbvvJvNT78XR22/IuIFv5YIgr/Yeq8/uNRaP2eDF7+Cns4/rDTBP/SdPT+ezeU+IrLxP5UBT79Jpzw9ic6ZvysoWL+HjAhA5O1PP93ktj+N0DO+kKoDv5qHVD8PBym/XBLJPehxvb3aCHm+tQMkP8xNrj6U1Oy+DNVHPw=="]},{"shape":[64],"values":["mW0WP9POBDz7UBo+tZYivlLl9rbZJQo+VJQ9PDKck71MWbk+ylrhvsegRL9ndUA8KrvCvgt/oT6H7jQ9g2jcuAPsg72f8ki+xtzkPgdtPT69KAQ8cw3mPt0eEbYBJlu9OLrkvQeUQz6W3wG/RpAuumUmeTkzN7S+IGxsvMvbVT4Xk+a+4vOWukciAb7yqaQ9JDhgPaiIujt60FI+D8ZEvhlgHDyo0gw+ZNJGPSAETL1KxvG8KHiEvNmc2D1CzwU/3t0lOBN+fb2Xehy+iiBVOIBd1L6s9Yo8ZdEGvyUMzDbadMw8aAwrOQ/xyrlz4CK/prbXPoLA4j5fdkC/07udPg=="]},{"shape":[64],"values":["L31nPzxXDT52x+o/jo29vleJkb8aWws+hn4zPs+3xr+/v8Q+xu/xvhaOm780jEw+wNLFvyXh8T66hY4/QByPuxBWj73/xAu/6rUGP/VL8z4bCE0+Fn55P/Bb7bzwR7W+Pffevil2Bj+Wpiq/xt+Pv+p40j+Ha9S+oruavkdo8D4JRP++LkdIvdyPA75sYKQ/DRz5PWP/jz3X4FU+jcn9v1UdMz/Vjps+MJMHP1l90L6IpN690vuOvu3YJz7mhRo/1RT9PeLtnb5DTba+XGCzPgMv4b9svww+RF0Yvz4yiz+mQiY/OF/wPbKT1r6ihUC/xKCCP21WSD+YouG/+iGjPg=="]}]}],"trainable_params":{"embedding":{"weights":["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"]},"lstm_2":{"weights":["vRSvPSk3a76xbQY/8p+zvjYQqb3T/ve+0X+hvoNixL5jQxC/I1m/PnLHlT5UXCY98iZrPrGmPL+aH7Y+I+nJvYXsh73TvuU9Yr0ZPQKsLz52cfw+oJtuPVHlq77sbxk+lz2VPb9ajL1q7189GcfFPSHc77yhnaO+HSnEPodLxr4pYZS+6MZDPk6Jf77/EgM+/CXaPv0M/z2/yJK+Bi12PrqBET5ArdM+nFPZPgq6Qr2grTk+jAsevlOgAT2GRQo9ozt+PhSvrDx4iVc/W4MUPueBz717N4a+Q5oqvrkeL716064+UjBkvepLrD3DLt8+97kdvrU8KL6v7XK+B+O6vlI9uLxdYos/qWk8PogIOj1KBcM+ixoCPmIOkj7wTn+988qNPYiZAr5nBMu+GmzMPSJFPr5SYB8+U61fPs6waj0Ud6E+4RQFvuFqn79+nSG+qQ2FvnAIEb7X0nk+eoTHPjgHq779WgU9mEOEPf7Ekj5W8UY97fMMPq9+1zsox+O9de5sPvgSOb6Pthi+boNAupD/qL1OjR6/VOWHPv4VGT9F0TI9cfPVvux84T3X+DY/b40bvr+txj5mNpM+bAymPgZwtz09JM4+mR/+vVSPFr96wFS/OluNOkiODD6rFUY/541dPufKbD56oJg/WSAOvp//sj0zhl8/8tBVvdLIcT6jUea9V8x9vquNhD73WL87ku/NvV/637x4fRQ/6q9HPuB72L2hePk+XOYhP/DS8D1msNk9Wbh+vnA5oj0D1aO+opttPstSPz4eeJC/pWEfP4rsFL4wNqm+KXmMPasZPr7EmQQ/Zh5yvVHITj1L19w+BVNNvhDuiL56b9w+dfMiPwQtPD9qGTO/NfKGviSdED5pvGo+GfK3vJNe5T6tgfM+BNXEO2Mjmb64QZK+KRTdu9PSET2O+pm+c6WAPo+Bzb3mFiG+raCIvsjP/rxDadm+QkIxP1oNY76gI9q8iCurPjfuRb+WAMG9e9+GP3l4gT0eQ388g94VvzyxKj7JYvC9","ygbsPYiN7L5aSzI/FR6uP4UoGj/YpGY+MwoYPsFtqj4CIsC95BAsPwFsGD7LrcG+GCBsv8+bmr5tmri+b296vt7d8j7mCkO/cjUEPy0ATz8xAw2/F1+qPUzHND5V5kI+X0AmvsNRiD7s/SE/EvNMPx1h/j9eCBS+8tkOP5ewir1JyqY8qgVkPjjMNr+mAFa9uvXTPpWyrL5sRi2/Lj5zPYy5QD70/uq+SdnVPOH83L5osI0+4rSTPSv8ib9YK3O/ma4JPsA/dL7xSR0+mcwMPyyIGr9t9hm+DAoXPygr2D7FIKI+VHROvwqKDj1Nfse+rSYFv7T68z5z6lS+Q0xIv719zz7kA4g7aN42vszcLj4cTjU9w+amPvsK3j03BnC+X1uvvSTq2b6kSnO9U3+Dvr6Gfb4dYS494HxTvntaN76gwIw+t6Z8PvrHuLw10ZU9/nF0OF+XEj4lIbE+SWVlPeO/V7xqy6k+fHOePSfGq77P9wo/Z4DdPEerPT3G1pY8AuAovNrXwjzLZIS+jBFVvhL9kj6vSBs/szpZvppxTb5y2Jg+cMVEPeI4lT7gnBs+Y/1OPjVLJj1pCnG+oeGbvr5X6bxuHPM+cY4Nv7wNdb4nm/a7Nu1avroZBj7QBYk9GqoCv418Hr7Jj/W+AB8TvTI76T6t780+nJsSPjNWIj+udMC9AJ2Nvdq5cD0zCaY9OjySPr9FCT2qGJ++MtAuPs9UkD7QTJe+tXTbPbAupT5A/J88K/07voSDDb5R+DW+JUJkPphfrr10hfC9qd8vP8pOuD34T16+WjyJv2rmuj6htzI+JBqfPLKByL6AQqY+8xx9vsfHKT/Mnh8+eRuRPOHh6D7aZrG9gkLzPZAIWz6u83W+OIiAvt7dhj7xOUc/8eAFvxeWdT54BIs+P8IgP9sXTjtAXX4+oe6LvhGjy76ey1E9cDrHPA8rJjyWcgM/Y0egvdyL5j50RP89B/qtPoinkj+0lmw9ULkRP2EPAT/9OPc+xDIKvxvk0z0I1Ua8","Nii0Pkbz5L7oSrA9MIqEvkgYDj71Yxo9smYavygY2D28BJI916f/vuseQD/2hda9Ua4Rvl+Pwj5Yink+vYHqPVQNqr5dP5O+3Mcnv2FxtL5Fpz8+ahH1vRMvBD+KmJC+WIs2vvOM7rzuhXk+Yg30PWZw5L3mVf2++xV9Pn0Isr7JqQS/nKm3PX0mdj4Gqbo+OrxcPorhsL71OKQ+FJ/dPhpjdz6wXdu9nDvjvkJleL71/pG9YpulP1gRAT4/jx8+RgpLv8awXb59nhm++ewCvuP/DT+GWa69WFS5vQAALT74j0Q+74goPgb/AT8v6iu/JzbovV17FT9IAty+IaPvvVrCQD823+a+DbbqvAClCD/NhgK/Lih5P2JKA7+wXVU/w1jzvTNzjz6N7d0+UyLSPucOUj82Nvg8QTIUP+GP1D4Lhw0/HngePx4AIr52+kK/9k1WPCcMSL76Him+STQMP3Vy4TxQS6q9IvLPvey6Iz+5mUU+mLuMPkIicj34Rok/sx0dPagpBT++I30+WKTqPuOyC7+cePI+6n01PyckGD4vtoy/pzaSvzJ61j4RPhY9OsVQv/shsz4bCJ4+ep2Bvr6B2zxpQVq+hdo5Po/4rD1iL14+n55DPnNt9T6VY40/2PUFPzZ1Tr+iiSy/TDsPP10erj96/cS9LdNOPi5RAj/Ww2A8i2YJvSzLjL4J1JW+n1BNPqJ7dT3KytK+CQZ/vo2cTz53Eom97LgKPrq6EL87fFG+2v/APue4cD0Ywea5UZ+jPvv/tb44Q8u76hdbPpoo2TwsDs09snauPrwrmb7wzcm9C8GZvRHdmb0oe2q9xruwvrlPsz7Hhuo9PNFkP814zr44Mfy+PkBavqFagz2ejDa+VqmhvltYmb4KK1Y9GAAgPoQ5Z78KuJ4+F6gEvh4WAr0dTBY+0N2RPpiJkj2T7y4/BWYWv8tFNr8484K+Sz6gPkOAbrpMB7M6sqf1uwwB5r7KDOA+06GevlHNRD0fuNY9RoI3PpK/bbzTnEI+","FU9AvvImcT5auFC+Z0cOOpdruT46dQU+ZgI9P3UM6L3f28w+Gv9GP5y+Mj8LA5K+r4Zxvh3+NT7HV+u9NQHiPc7Y+z7f6y4+uqSGu6p4Rb8QJLo9Ki+Svd+Wmr8Xtn481wF2PrF3Aj5Vix+9TxqlvlGG/L43wNO+HSNAvsMBHD3amta+DRtePxtFvj4dgQ+/9WbiPhT6ND3BBv6+C9+Kv9jUUr6j2188gSeKPlyYTj/MXRG9tLMovgIFbr41OeG9VzNqvtnbOL0qEk8++MSGP0KE1j57Zpe+Y2lHP+m18T3ynxc/ZJzGuxJhAj+uJ64+DDq6PR0cwD26EGE94CAZvDtNtjzTDPG+gSzHvtK4BT7RP+y9PPWnviIDmj7asEi+Po0XPiu3/juUgpy+4zDLPoh8BL+p6Gg+qJs2vi0M8L2Mt4o91l5RvqaXt75KNxE/Ia/0ve1iJr5nUGY/erZCvb6Arr6HW7K88qYqPpJ0m76bNYe+ZHsDP8vEiL6tkRU/5sB5Psya4T75zh4+QXEEP5TbfT29NAg/KCKMv6TAT790vHs9i7/pviOxGb0nLra+aa+yvXYR2z5WFby98C2TPqWVhD7cQ4c+eIYUv2kUwbyY6hy/VdkCPmpUXz9jfNa+Y8gqP7FTiT5OC+e+YkYDP2ES5r1fFxG+91Navgi4rz34O1q/8Q6vvU05xb+Ph66/TlmDvqPtDr8LsLc+fAdov0L+6r7Ttjq/eXf1vnyEfb8DUe6+DtuyPswNPT/zQfi+fNH/viW+Nb/a23e9o1UMPwvXUD81p5I+Z2xQPqp6zr1whEs9l42RPcRpPL6WnGM+j4Gtv6Nc0L44YUs/2THqvsdVRL8184Y/6JvkvjlbPr+nT5q+rMKevjoueb6rqwg+8UxLvwMeBL6WZk29g2BBvUcrLT7Rnui+NrmXPtqOmz1o/be9J20YPhOpWj/M8z0/8UtwP+5oq75AM12/ctlhv5fyJr4qDOg/mOm+PeGUPD8xFY8+YIIGv4K4Mj2vSqO9","kJEkPlwbzD7P6SS/6mvRPl1WiT5giyU/s1SiPQaFyT5bn0M+zcXmvYvIEb5FBKm+iK3IvDzdLz8RZ4y9kwIjPiOhsD6cIKg9C6QgvhgWPz60C5i+468JvkN9sz6XQYY8aTw8ujXzQT6SZ+08TL5cvG3Bej5q5Ic+eKr+uxARkj5f4Aa9Ub/UvYQf8T28y9U+s7JQvqaFwb3VF5O9MP/VvheEVb4zO6a+4igKvqRJpD0MRMS9qsyYPUqyuD03iwO+XqiZOxEghz4lHQO/p2vGvtMMdz5TFYQ+zG68PkNpDz765sK+GDiTPM2e6Dtsvpi+YDdwPKvvSD4ztZQ+g05bPqUnLr5JaMW+mS0HPhsTMT4icWy8VFUZPs9b0r5N2aI+dPGAPuEwOr7CZAc/3ndVPtxSrb2Gkre+CQ4OvrLvhL1SBu69mxaQPocAfj9Odui+Tw1LPkdRDz4V+DC/JUlcvkYcoj5bBA2+NPagvhR+lb0HW6++F3OkPdl85b4RAJC9Y5EtPiB7Qz++Bqk+MhS5vgSDib6yl929IRFIPYZywz2Ub22+V38rP72ZYj7o/Ue7rVqHPbPNcL4Kzq08XCVOvnukGz73jWq+oaJVPoDQVD8q0T0/OPr5PYjFtj3/CuW+gHQ8P+Z3/b38Cya/bjwTP0t0az7y8jC/RthNPgeqMb4ZSo8++Y1BvgvdbT1nBko+bIDVPCxmXb7jNxG/XiYWvuMqgT7CtBK+i7eVPgoelD1OUFO+WISvPoZdL74V+pY9lJW0vrydaj46NhE/OekxPuD75TwuIP8+ni2APvcfgj7yxTO/ZwRFPf+5pj4JO4C9t4hNPg/DDz+pKey+uVOrvaXUPr/Yb/c+rE8qP4dcWj413DU+5sgRPTo1Fj1w1iA6/6KGvty5H76hgBM+S94XvqVYsL6vWic/x5Awv0ryPj7+0J2+2saqPn7tJb+U82M9YNLwvqsyPL6bA60+IoVBvsByoz5ivYM+kSYev8WuFT4bZzS+EVXePofJtb4YV/u9","fTYNP5441j6MxGa/mBZuv3xm6L7C1OE+k7tUvsQD0D3EvgS+XtmYPfdhbT6nlAM/UNt4PxfAPT7n9GA/tm4cP5RH4D5H64I//YtgvmnUn76Wkxk+noKKvra7ujwuK8g+AvsUPxMgJr5qpqa+juTOPVjl/L5KvLM+KCRBvyPLIj8G59I9PS0QP4smkj+YbCO+Nrs0vlR84z6k0Rk/DMz/vIn0E7+7Dx2/+8Q0PrQRoj5yCZi+DYLLPsH3fz/9W7E+cIHNPrRX1T0i+4A+zn5UPro6qj6gO4M+t55XPZFKUj8jR7Q+884PPjgvfr5Jm1E/AXCWP/CdRr5Gijo+XKFiPwQw270KBne+keGgPGhiTb6UyOu+Zr/ZvtsNhb1mmZK+QY4kPosPHT0QpYI6CfnqPsBWDz4REzG/hGMlPedubD0uVPS+wNGnvR5CsT11akE+d/XMPU54XL7sVQK/MRO3Pr0jjT6qYwO/rNxuvhJoGT56dfa9HDRPPUM54r5bZE2+yc1ZPi9r8D37CD29DIHmvUVrV76B1BC/kDLNPmqdlz3+myU+s6TbPvA0Jj6jvZW9jD5nvsyGI75DicY8Gm0wPftzZL7U5d2+hN1oPhMY2jzZR2o+o+PBPhtvEL7KvBa+XjEZPkJ1Pj62fQw+zAO3vNj/sb7Llte+K9+tvkSLrr5rpK+9fMLYvLFXPz6MlVO+YY+hvtvZwL651QM/splcvrFLsr739zk+kJkOv1sGyrz701g+s4HSPvWSsbxi7Ls+PgCnv9/dhLw9upE/XqWbvvHFrb5EFoI+wxyhPxbegb2EQya/ncQmvqWKTz2s4J88PRjpPshzBr+wMY8+VCmyPcusAL9n90O/z284vuodnj4RP1W9FxDrPvMGsz3g7F++y/xBP/lnkL6Ej/C9J8k+vtAsn74N+SU9FFI+vt/GGz97cvy9YS8gP1c84L7IkiO/ZZoQviVsar9m5vW9D1ghPIo/n7/ShoA9XinsPRhsyj5RfIm+u13yPfb6fL4mkSY+","zPiJvXTe8j2RO2Q9EUNIPUCETD517vc8ovogP3h9OT9ow4e+xOYZvTiPhL8HR5a+ecMVPoVeUb04VuY9raenvSoQ+b0ssYw70cBAP1iCq72zHdE9UGOOvj8KNL+RvuO9exoqPyZAfT6rC5m+WFCVPaHiCz5BR5i9zaO3PloXHD6BCpo+M3VCv0PQPr8IhbO+59ElPqWLsj67EBw9d1gSv4MBRz6L0pA+hKkDvtweXT7X1i4+uqoBv4V7Ez8zd9G9ZvLHPiEEqb5j8+E+1P7XPqZ4lz0xx8G+xy+Svmw2pD4s/aS+upihvRh0qj2yjg0/FvBuPjcagz42pqG9EUWBPZYCRL+Iq1c+ePH9PlbE/L5MsJA9zQegv/FBIj+vcyS/k54APUM2tb4BsaC+KGU4v3fMKr8mUo081CbgvqK7Cr/7WWa/cFpfv57SPT7xZsA+zBDYPjGkkz4k9Fo+QA3XvnX73L6gzb6+QITQPdsA2L58Owa/xTl2vQa/hT0pSFK/DngSPkk0Ir8h2Cu/4DoxvX+Tnb0qf8C+hwExv3uJOLzoti8/acaQP9oGLL0pF7+8syGEPqY8i75O30m/NIoZPpw5Jry69bU9cIiqvhHtbr6mFCg+LpW9vmmXKr4nhIi/+nUlv8FiCj81f2w/Wp8Av6Annr8Qmcg+smmdvrakd7/LYEe+76D3PiSeZzzoRpY+deyDPn6mKT9rvqU+fNIWP+P5Kz3EBMw8PeKLvtl8fz7Uk6E9Ks5WPuqd4zzImAq+7ZiMPtofhj7qF/E9/8SivsCq0D3cHxE99dJ4PnYaDT1MdWS96A6EPpQ/oDu7IqI+7yUuPtW2Cr/rFgi+yQduvgCDlD4T2wo/RkO3Pq+dLz+N8DK8Xc0MPwf2Xj5iuMw9/QKVvj1YrT4A3u6+VM/wPc0lPz5Rhiy+pkm9vbSYDL9pACu/l6UOP9LDuT5fNLY9c4K/vV9kwj14uO89ErfLPmSRdD7LGGG+t1eMPvMdzj2ls9Y89aRCvrSh7z3vRJ29","uRMaPaAl/7758FI+xBWAPoe3tD1EDRG93wA+v406CT/q4Bq+6sCMv9K/bL4s6zI8dsuMPiBIJj4HGuM6oWuJvrp1x7zfEmK8UiOdvTz2uD4MKgC+9XSmPqZmET/BSU6+nDLnPnrMtL44B806gdu5vf7Urz4lpiE/CUJrvg3I2TxPJAI/+NQLvkTogT5oZiQ/6Dy1vqYYpr5AFjY+PE/QPmISVD4Sf5M+sC4Vv5e5FL8Dv5k+H3iMPks35D3AqwK+GGoQPqdsDL+XRtU9AhU7vbtkiD7Sui6/b/89vxWBvr6rNFW+Ndr4vENWjL8dTwK/vmQfvvY7NL4fic4+Bb0wvEqDxL6bCIo+gMyNPgk+aj2VArm8L0RyPlzI9r75nRq9+huhPdzi7z0J3pQ+TDaivrp0xj4lvjO+r/p9PtSGVj6KlTQ+reytPiphET8APsG+C1ImvOgZJz9BO0a/NXwTP7cwN75cTkC+F3UlvkA16D6gURY+b+KLPbEJ674u6Qq9v+K6vjEEDb6SBvw+ahQSvrQQ/r096Eq/7qd7PkA2gj/3DjY+nODdPjkBpT52iYI9BCyVvu+yKjzexjO/do8KPrkMr77P+iS92iHWvg5+qL11S0k9dhgWv9fjb78qtna53qL/vqTncr6Qrw+/gQmpvVELGz3w+ck7YmbBvTtMKzwW6C0/PsrgPfNn7T7PObg/Bb9jPqMcdT/qe/k9CGQRP2+RFb3G0nU/lwz7PvqLoj+024Y/XjcQv00N1r5psS8/4AUyPzxOmz8bt0e8OD8ev9NNQb+OauO+fQ/PvOWrBj+cIMu90hivvvRtDDzxKkS/sxPSP+aVSj2goYW/ed2qPpmGPb5nWLa+mocIP4+DDD86Ry0/gtpOPzJz/T5Jj5u+BUTOPo5Ig74OtUG+Gcd6PlyKh70OA9c+NPsEPztq0D1gT6s9piK+PHGAur5foEi/7T1GvwDXCj8U6QI/Cc6WPzydnT43Ddu/Sccivu92F77Mt0m+bnfMPtCpCj/nFow/","kNwFvRtX2r4kHc+9+uUMvmguv76JGyK+Yqn0veHPgr4audA+yVHcvp3JGD35I5M+F+GlvV+W0b7AgBm86ZrAPYS26b7UGoe+NK0PvuHmDL5ZLSq+R9oLvvDTCb83VUw+vGC8Ol587b4RquS+VJZYvSJ9q70Z5Ac+YEbavsxcQ70WXV0+qFClvI921jwmAxW/VYSxvuUGt745l2s+ET9JPvaayT2aQNY9ZSS3PaE+ML4m2ka+4ksWPhz6rz4KdrQ+OazGPP60qL6R0bi9XruOPq2Xbj498JI+Qnf3vDtQYr7bpbi9k2JVPhp0Nj4j1c6+tdJivlkhIL4R1Zq+2P+zvtRj1z2dPHu+M8kEvdbaHr97L92+Up76vqYF4T735xi/775ZvvEFXT96jeO9RhuGvq5czj4tbSs9qhwBPrdQ6D6RNfe+J6h9vuk5Sz+1txq+pItePWwTGj4p6ys/QL9NvnHNcr76rpQ9iSfCPmHRKb53h6U+wbpzvkb4GD96/wc+DRMlv4uuTr/ueva+SAQrPcdyAz/kL/A+ERdavn63/L5dzg++AmaNvosbdD15rKe+2ZOFvgX5yr7Dqc2+gQk6PoTUx71xLHM9RXImviLMBL9x/pi+9L0JPty0hj6X80G+YLGHv03har6S+II9SbWBPraExL6Cngi/QtL8vk2tUj4Zt80+FPdjPs0wIr7F1xY+Oo9oPhBCZ77UME4+w/zhPvcu6L7pb2i+vh5RvzrmIL4sVUI+gZKWvZGg+D78Xrm+mxoQvpXTQj4afh4/7kLQvQjESL4Vy8g80ztavvVzOD6RiAe97J4nPdOUpr6Dz+Q9nrF5OkMADL4L/Tc+mrHvvQEkNL0hsXO+eR7lvoGpz73Ovc6+kl5rPyk00b0L9zu/kPN6vyXG5j3tNQ6+h8ljPnlkyT7sr1W/Xtb+PlCdCbzu7FI/GKBZvZ3x8j24NS0/wL2iPT1MKT0srUw+KvUsPrQ7IT5olp++VpYmPtmDwD4sn589LpfTPp6M2j4Gx+U8","43QRv/Tz7z3+vIY+5G9nvxSn5L2C2Yi/Ia4FP9XPLr8HYcA+XIr+vjbVJL+TMBW/7X39vqf4nz2Rr7O+dYYzvkCjUb9QsOO+wpe9PYgjgD7+0EA/6rwCPigfUD4cCDu/EmL2vfFVd75uJEq+8lBVvpJHS78YzsE9A+eSPu1seb5XY5Y+4DYyv2kYTL6h2Iu+katZvjynl76zoyC/txwDPjSOOT+t5J0/ZEeCvuFp9j6TQMk+l0eIPH57Z762GzQ/1FViPhRgUr00IjQ86ieOvkdAmj6nKsy+wc8cv/Dwjr/od+K+C5p7P4iDhD8Qwfm+7Tc7v9k+PT2PA9m+ufWmvrgWqj48yO09jYcBvwgCUT7E9D4+QjqiPuahFT3LqGQ+rlWcPuLzwL6RhZm9Gz2wvXFan76bhjo/BWgdvkhhHD7JIuA+rNwUvXiAG77eCXg+xV/nPKQflL0Kcr8+KqKaviLcLb6n+sE9GQlOvu7U6b0lNkY+ZdYLP5/Cu75JGiM/pNDgvRWUdT4D/lM8T5maveo2m77g/Aa96lhVvmeqsb4wOSk++zb8vry1wT5hM1a9GSBlvsWeDj/iXGw+OSBEPtDDPT60aIW+fwIJv4YSq74enho/LpJCPudBHD4gGY69rOo2vqCHRD58V8K95sjhvm8ZVb0jtrE+dLpxPuWjjz4Xndi914Y+vjN4sb0IZmQ+MS5pPgp+fj4LhSk+i7CzvS8V6D6cmYo/Qi8/P+On+bwdZg89B5WZPgSrBb74A4y+WW9cPkNvwL2NK5c/aItKvrxnxj6ocyI9xIA5v5uwzr3oDaw+Kky0PQZOa7wbgiu+pGuqvuDupL2orPo9IC+xPdIs7b7tQsM9HIxzPg0VA74mSb8+8CVXPu8xK753rAG+FE/Ivm31Hjs9Mjc+HqIFP11Zv7uJq+K+P0QYvkW/H75/dk0+NmmMvov2YTpfcrI/fvXrPq6gAD9l3do+JgbhvAO+oD67TDG8le+EvS0/7D66oQU+wanAvmvYZb428Yi8","oQzDPneWY76prk++g+iUvdMrrr246SW+eGo8vYDp0L0uls293nimvtHVIj39taU+GcJdvtLm9j0iA5G+ZVuXPtojkL5cQqm+fJEvP/9qJL6QCru9v0+cPEtjMT9gsNE95/sFv2YitTlQdVg+gRKSvkxu972Visk+I/xWviI2077C9a6+4JYDv/z2x70KnpA+812TvNRMEr6YfZK9AmFVvwRexb6QT9s83gGVvmGa3r5DQMy8wJ6kPrnL9Tw4h2s+ip+cPoL1Ij1Xb8K9Gx1IPlt4pr6H1EA+1o1tPwUcMb41UUQ/6S0GvTYLD7/Cc6A+oTcMPhknWT5DJwm+4SgXPv2I677Ucbs+nkeevyWMnL8s9jO+w0tcvV0sJT3hWIS+ZIkHvu92EL8HWwG+6HOxvi6HIj5nmyw/GcwAP6lLPD7hgAC+zpELPeCEQr7u7cc8RFkrPx1kPLwh8I8+nwXsPTUXXj7YLTm+/guCvuWjp7skGYm/y7A+PlSQ1j6J1FE+7ZFQPlkoLj9qlQg/tfDavnnv/L6Q1SY9AiIKP7nFzj68aCm++auCPtgbtT4K2Zc+XjJMvpkVwD0unwI/IiUaPw6Fcz6TC429Y7hOP/yC/T5HhI0/XgPJPGTlCL8EK7++MmSXPoDNoj+ZgYw+Z2MVP31Ohj/h9Au/MPVWvRbExj4byUW9WZC/vjbOIT4ZNJq+OjMFPnr0Hb805BG+2cWTvV+Yfr6SAYU+6N6FPnRnJr4RjO88jslXvBBguz77oVm9VxtsvmRKzb7y6Yu+sTQkvT0flj5wV1O+HpeDPacvrL7ODom+2rSVvs6dkL4EKJ69EdNCvy0LHL1KyGc+r7awPVNm177nmqe8JDHDPe4N6z6gTRe/xh8kvzfIz70YEkY8l9AcPhlRwb7+YsQ99JhavgdSSr5WEQ2+eseNPoNCyT6j8M8+zCyTvoCxgz5cQ6o9MldIvqZeOz1pUpG+7//OvTaxez4mjUU+IOK9Ptg19T2QAeq5Fck4vSnKnT729Ya+","bGtyPqktQj9LL+i9bP86Piz7Jb2860Q+q0QHP/1gBr9ZAwI+xv4ZP5Yo0L2i40a+t/ZQvtlquT70yMY9Rk6RumIeWj4M+co9I01MvxdTNr/B1zK9tNf/vcqZhr6OXLS+uS4AvoVr872LZAI+QT6rvhFthr7BTLC+K1Jevi0inj0PJuy+SsKBPhCjgL7EAgO/E4XBPtPt/L1Qw6K+dsbBvg1Dwj7OAYm+ub2mPffVxD74Myu+idvcvCrACT6Zh9o9s4Z+PSV5lb1IqQ0+QseXvsWyIbyTj8I+mrm9PjlctzojnwW/voi7vS81sr003RS/1uTavUE2fD6jPiY8F4gfvi3Bcr7VMoe8OEirvHjfPD4HOfK9eN65PIAN/z7hrly+7JGaPfGCiD41xKC8ivUTP4W6Gb6SXza+qyabvsxoxb4o4AG99L3qvrOOur7cBBI/Nc9ovgOTIb4j+8s+sK+lPYKjYj6k2/s94dravOOlEr5RNeK9BbMGP/eLWTs6/sQ9OickP1Xs2D0UYqS+Nz1OPi89ur6vEv09aV3+vSVYwL6V8ZE+b7Bpvd10qj2Brvg8MXIvvmdXs77mXaQ+FrGavn28TD8xjHw+eqowPZOHir7YlVa9YyqqPnK7/j4eIPK+obMgPjwsZr6259e+zdgBPqrVRj2SxoW/Nt7rPT4hWj3hiIe/2ZC3PtT2yT3unXu/fmApP/Ywir+56lS/3rBlvzc6xb1voyW/5sOnvrSTa7+1lYu/DV74PoMAeL4d5NG+HJxtv4zdcb8WB4c+mfhCP5ihBD6CRmw+XVd4vd77pb8zoIS+tmObPrdqsr3w87u9d+Zgv4n/l77NVLY+Rt+Ov6uuz76QFKg+TnI2vufPEr+jrA49cZHIvrRjtr6WUzy8ma9gP8qQ1j2koYa95FhTvxfk/D4HI1G/6KNOPuCVVb1jt709fIeEPqrN+D4/zkS+BP07Pj12sL4Mfc6+b9GNv6aufb6jdIg/n/zuvvKE7L6dE2i+W/AgvqXqIL4RF4W/","2IYRPqM1dT7iqMi+PhfBPddM5T3DFCQ/Q0v8u3XcTz48RSI9PDK6vcvDvj1O27a+l8rivWqaSD/0KVq+79oSvKO2hT5JwaY+mRCVPebBRL4ULli9mSHvPsR2ID8jcjG+kpzzPNc47j46A4M+QZByvk2gET2ftxK+ztTGPhnkzD7hpJq+8GD5Ph3+aj7duAW+YEQHPtIlPz/o/+S+I1mivQ2llL1iP+2+6IZqvjv/OD35Jus9qjKgvV+vy73AWuu+TcJYvlt42z4p4bi+3oW7vsxDDb6R5rq+zFfIPpv4oD74Eee+kzX8vQHFhb5b1Go+tGdhPT4Vwz6narM9U6xKPgmpSr3+l8S9NwzDPEYfgT51sDs+ByqOPotaP7/vt5U+QrCrPhlPJL/DIoc+BrIUPkzirr44T5W+I1VKvasvJDwGSVU/gFGAPoGXsL5nQfq9cZMvPu7Sw7wkxV+/sTIuPqA/3D7SvaG9g7iwvgUA1z1iYuu+j0sYPrXjZL8+F/69EWsEP1rbtr43a+8+QjCKPlbd/77qC2C+xVeAPZKMzz4vAqe+VXzWPnF8Bz5a3Ik9ve3lPk6Ir72qVTw+XL+5vjDi0j2U7o2+m7rHPiP8Jz+ovg0+ybu0vkOsm72gU4G+hIaYP9hmoL0HBK6+aRMgPqsEKD6nk8O+OLfAPo2R6T37yI2+Vx2tvncLmbt0UdA9VlkmviYHJr3mdhm/qNGvvuOZyj5tZsk8d+5NPwoVQDwJiZC+75FvPnSF/L1G71I9+l5UPle6BT862wq/gfUVPJZyhT3pz64+jlohP3Sr/D2Y1VW/Q7RMvSlcyD7He+M9zr/tPIYmsD4APaG+172UPvlHvr55bxC9kgGnPyG3lT6YhN87JhU+PqiZFz4muPg+ASVNPLBsCz0dv308i1uxvuDKj75/nUU/RZJBvyd19j50nUa/pDqWPsNJhb9eTM6+RKRePsM+K79ZKuI9CVhTvp/bDj7QYBM+BoAev0t9f77l80W+AX+3Pp+4qL7TErW+","lKtYP9/xUD467oi/2Ir1PpyAID7nl4Q/5roWvlltEz9hAoy+kuUSP+a1zD3fbzg/Mt6HP1eAqz1nq0k/qfU0P95vTT+han4/0/u+vglTG7+SYHm+8/eBveeEmT71MI4/V/yLPorLEz4j7FI7HAIsPpAyCz851Nw9CEkCv97ckD/1LZu+HiyIP1XaGT8m7f29l9IyPhRlCT9rzm4/RnZPvhAggL9dLE+/ed8lvQjSpj4AprG+vwR+PkXOlT/Z8kq+JCAXPgwMgD79qOI+bturPt09jb0x+JI+0I4wPkLZdj9a/gk/+OD1vp5zCL8c/4M/Nye9P7l+8r5WQfg+XBtwP04LA76QPww/V+7/vF7rDj2pxqe8Z3PYPji77j5HXkA/mLAqPks+Az4x9pO9CecZP+rbpT7WSQc/0xSMPmmQyjxEJNo7ODNKvSbDg77GXyW+CR1tPJEOkz4XYGY+U7gSPTux+r0Myzg+A+iUPbRIPbviHgc8HEDSvtqyjT336PW9vf6uvh21Nj/Kizk/FchBPwCuoLyohkM+v8Nevpn2Kj5aHqS+f8aRPYkfu77HdSC+aM0jP4Jnpbw/xG6+dNR2PnLFiL+Ov06+TCQqPwBdV77fFDq+8JTuPREdc7576Wc+BuWbPp/5Fr9XGgI/7g8AP9QYWzyqu968qMadvjYRoL7NzyU+xluSPX/8crtCAqM8NEEJvmz3dT3Z1hC/Sm7JPm/fFL6pXTC/Hs3evZU4h71IEkK+hD4RPulfsDxchfu6BAimPu9gF76u2Qi/V2Mdv1pFQT2FCWq9wIp/PxthtL5K4gU/gE/jPYPCDT55q/S9pFIfP0XXpj54LzI+8eaEPPtNvT2AkzW9lJg7Pr3NlT7+AhE+ydyOvsEmEz/+fys/21vRPeTzNr3swOy+NcAjv1vMWT0zs0O+R7OVPrl5tj4vy1U+wlyhPiztCz7jedi9cjgmPhKcm75UPJ2+Xv/RvsCWDL9vsFa98bkDv+4Ag7+G75S+fpyTPeyWtT7+qKG8","kHU2vsu97T3x+zc+WhFMPsfhE75qpxQ8lvrGPTRjwr5lgJa9cNCBPi0k2D4skRW+KxoCPx+eCb+SBY0+iZNAv38r3j7P0EU+Cy5SvqCVmz6byYS+DtvdvSU2Fr+dP+E+lcjOPSH2fr6e6AS/z6UdPt5WVDrYNdE+A4h+vi8P872UCiC+kwkFP91bdb1W54C9pnoKv7j5ET4wtxY/tX4OP+7rkr4QEW89df8gPwCgqD5eUAo9jfiFv6nR/TlAnWW+IWxAvqXDdb2tpiq/1xDdvUlADD+05Qe+xddDPjdZ2j2B93i+7dJevlrV9b4A2qI+VJY+vvj8pL5PBXk+a8zmPfkUwL4VHbg+7SJgP/aCez8VzHY/gKTcvZ463z7gp6I+GxWkPkr1WD9p0O6++P+XPyrdeT50x7S+xaRKv6qilz5YbBY/TtUiP7xzAD9came/n18Ov8uVyb5Shoc+b8QlPzRS4z2vQmy+5iwLvsfpO781I7A/aCuZPlE3Vr8Gi4E+ZgEPvtKFbb/1uCI/Ek5rvhdfVT+jFe0+ORJCvrs4Ar/df5s/MqU+P+0QkL6ECzM+9MKQPwIwND3WrJk9jlwtP5gAJj4T8xW+EElPvjVlW79iv1+/Xcy3PRr2wj40ubE+RfQUPvjTbr9aAsM+i90rv30bOr9OVOk9IKS3PqIgEj9uryG/b/iEvKistz6xtIm+j2GKPeFUAb/wR3q+JDxmPoL8PL4kX8M+dz+HPSaCMDz8jQ4+piWcO0j1uT2zMwc+3mNSvoH//r0YxWC+q/+/Pp56BD7tlru9bsjqvMD2tz0dZFy+PHKHvhepeT3qzZI++UYUv5MYKr42VrU+3okpvbZ/LL7RN42+3Sz/PkhKYD+b196+JO7rvmPiNjxJXCc+RIEavvvFo777HcO+gqalvtnuO71rp2Q9x8qpPsa0BD+4JY0+rX4Ov1D36z5PaKc9oCtiPhhJfT5TRSW+IIDLvd/rID/4Qqo9RkzJPj40KT/RBqK+z0uKviOMir43kyy/","joyPvc0arT6S01Y95oeBPf/iqr4G+z49NVRGPtfBir25Px+9JZVoPp3Fy72TvZa+cQm4vXlLEj92hUk+lnUAv2NuIj45U3O+xMWTvhAaFL+ihwg+Udw3PX8lIT9i4ja/O+BsvbVXLD6qTz0+JYO1vkbcpT7Vdk2/HsfHvCFckj4Zv1W+QuLgPova7r6TjA6/xBpdPiS3Er+sg4G+sSAEvy/EQz9yVb29mTTEvVF0or5oEPK9w21/PmsojD3jE5E+RR8EvpQT7z4/vvO+GymIvjRFXT6qnJW+kHRZuok4+74TBhO/HmYQvmRYEL7ONlO/8jCpvsqjyz58dA49WvaUPLXq1L4zER8+VerwvZnkFz4b52m+uzJUPUnnGD8vgxG/QwT3vRfKCT/wMCi/smzoPWRrFT5cGnS+a2EOvtsLKj+dOvA9VPsTvx8CFj95Gw8/+kuDvjYolr4RPxw+oQ2cPR+xUb2Xi5C+eCPcvpOxWr6+wJu9ofs4P+zyGr9zqg6+ZDQ2PyG18D3h4ci+Tk0mvRDBWr47bY+/rw6uvg3zA7+a868+at4oPsoqpz6Nh2I+GkkwvVV+Yr8LaNo+GX3tvh8s3z7dtLY9RvihPjsvtL71mOW+bS+qPoERyT3Lole+ZZcMvwNaMDzhrPG+ueX5PF1GTb0FIC+/xebhPquUFr4RP4q/JEoZPyNonT4TtoA8LfByP3lBhb+GY7+9Hpt0vwuMaT55hK2+f+wIv5pq+b2I1Ey/LQuKPkGnF7/g0eS+z9BGv+oFML+oGwM/g/ccPwOOYb1Zlq4+TahCPaVdUb8htUG9d+wnvZ9M2j5Qbge/0FCBvt7rDr964ug6dCKyv6Vlnr4VfMC+4xaEvoS97b4hDRs/7FGavkC1Pr+G9KC91DCFP7PmDj80QI2+Bs5HvgdKeD9T99C+V9kZvtWKfz5fPNE9NkbWPv691r74uj6+yinIvl1Ezr5+s7W+/DiZv3fPm7745uY+XQfkvYGgK79+8Z+/gbp6vUhhxr3tFFC/","np3yPfNARz4lPsM9BEGvPjWWib0YGP8+abXDPgGXs73OhFO9nSPnPHgVy71qSau+eXcVvmar5D1E/k++GZyavAGypT56SAw/SqKgPMfew71qAoW+DLYBPtbG1b24HNE9Ix05PHq1YD7qTTw9yR/QPP52/T7Nure978M5vjn8Uj0Hp4I+WvKxvZYzdL4UJxU+m7TnPl3qLz6h+Lc9zSWRvnMyg74ymdk9WiqvPEp2Tz7cqYw+HujEvLq6Bb4fscq+yLaJvngbDj+4TMO+JYyAvug9tjts5ZC9OWtBPkXbwj0NkQi/hVGXvtbr277eo6m+rKJ2PXc2NT7i+268G+73PiHlBr6kzf++16s/vZ+/vT1Ay7w+yIiRPvKnDL8Q9hQ+sza6vdh9Mb/ws/k95kiJPoNq8rxUWcG+qBDYvPEqwr7PsZG9/6qmPLCd8z04Kdw+TlUbvrFUqj2Vb7S+XPiRPcD32j3TPZy+HfklvozdEz5szJe9e5RFPyKPPL6FTwk++22DPjOk5Dtwj44+U6mqPZoA0r4g17e+BaNfPkeKTD/r8Ci+FYyePj5xZL6d1y2+jCJfPtkFxT6jx8w8NRSFvpW5sr1iy+2+zKKPPmkzjT5nPcA+/0KKP/bfy74a95U+8zsRP57YdT2G8x89IxjDO6yszz4wAZq+oSqJPlEKb74CzCc+V/9JvqIL8T0CXre+CZeYveONBz5QmRi/JCuXPrNLHT7b59u9qzoeP1+/Gr6RQJW9xQLePrCn+TyYYAM+yWXIvm9Xsb6whMe+GTMuvn6TwD3emHY+M0/BvgB9jr4HFZ4+DuWBPd2VFT7DGLs93xZUPgQ1hr6swpM+Snvkva6nCr/P+Te93v2GPtp6bL3733Y/+4kVv7lLZD6MjRM/RvO9PicuOL28P0a+KE9FPUyEn75AbGo/oaLLPDRxLz5B9R2/9BQivsWRWL59BTa+nQwnP16J8D4WjT+9EAvcPilsNj+OcLW842vsPka6S7/dHRu+8EKHPhM+Er+jewO+","5iI0P/6gj77JSoi9FY7sPjqgub4ZWXU/FYFqv8lBUj+OlwA+xJRvPmZWKD/2P/4+WRHUPqJrfL7NdyE/MdsIP96kMD9vlm8/OuCevpfprr64Jxa/7/qAvgqUY76O59M+8hExPqrrK74nyCG+fOLAPfxktz6a6BM/oLS6vbIcQT8sPXc++YiwPjYTFz/J6Qg/OiOcvl2gDz/RUjY/pxWMPsCll7++lXi/B4vnPUctcT21tIS/E3LEPhnVpz40vQq/IZqxvhTvNr7Ee4++tpSVvvvpxb5ED7w+f4kJPyk4Wj8xWOQ+pwdlvxDsqL6mYxs/lII2Px5w9j22Stw+X8wzP8r0+LtauFK/62mOPrZrmryIlZm+dEwQv90eOL4PCeq+cz7NvS7UKb5e0to7/WutvlnbBj0YES6/Z9btPH9BGL4Nhoy+sn02viKFDj3ZOJ4+Y39SuTU6xr7MY3y+YamJPSO1fz7jrnu+B3Okvn9K1j0R3BU+uPaCPgFxS76uY5S+/5ulPkT2375IXEC/ZnyCvyUkfD4Gehi+6IoHP2cKEL6XI5c+12m7Pjc/IT82Be85WjOSvlCcyL1jPKo8gdTyPY6T3z5AF48+tyFnvhSfVT7aEBE+3vRgPnCOxT5a6Ui+Wf1ovlSIyz59kN2+t9CsvvonJz4lxkC+eq67PZ1fyT6t+Rc+wPKDPaqyqT4qGoG+xHWzPUupIL2h0w0/PrXwvqeEmL4I7kM/lYiMvXlK8r177mc+V6ocPmm+Pb6MKK+9ZUsjv14P1bysokA/aL4MPxMqhL1jIyS+FuYGu+218z7WRgG/58mePKCwMb6CjoE+RkEAv67qUL4ldwM+arJavi1+Fb13HBO9W8/OvgY6BT4J7Bw8GQrDPu57xb7Mvw+/Le8tPeowBb2OzZ8+rgA6P+2ZULwIIQs7qGhave68Lz0mQwa+llUrum9zJ7+8jIO+PMKivZYWjL1/P8U+6LwQP9znej1pKa4+3C+LP+KtmD+FiZc+AGBnPjipp77mBA68","uTuzPkDocT3dJMk9wkVRvrNpIz5olRc+kKlhPhyqDz8MXOk8zFlIvptmAb/wKm087vu+vsfJzj4yRNq9L2lBPkfZhr4AOJI8RIoeP9FTnL4HLcU9e/5dvRjPxT2WRP29wcn6Po/JXD7mC6I+RDI+vSqWRTxq9A6/liryPsR7Uj5lo9k+RvPNvkXo4r6aBMY9wnvzPrCubj5XwZ8+xP0Iv1RuWT6CzNa9wiw7v+uTdLnOfoe9mrZMPzdNFT42IIM+bacIPyKHFj1QO2Y/z5XrPp8YJ786s1y+5PvXvbbUUT4nzjG9SjUAPqweSj94U0K943ujPSaYjT7CRZu+dd4dvgHEhj5IdKe+4MOKvpnEbb/du1i/QSzvPc25Bj/KCsO+ARoBPvx9L79Ng7G9PzWev7ZfXb9bXwo+WjMKP/QHG79eXB6/p/+Ev929NL5UsmI/d2JkP1rZ7z50iSE9wSK9vkQ2lr5Jxs0+SQLVPSRZnz9Ojnm/umzzvV3Jbz+d6xS/aLe9Ppv9ZT7MjI6/vZ2VPakyAb9L5XK/d8WtvtMPpj6tlTS/9LaBvpTblT2akx69tfS0vlWFQj70ASW//mmkvu80V755enm+mXO7PuQTUz+Ag1E/qnGyvsHhV75E9VW/jd2avp1nmT/X1KQ+UWwZPq4ODD/Zy9Q9PWklv5NzP7+oodg93QlfvkP6nz6CQy++xFkrvoNeKb9h3nm9/UZKv+JM6b7gvp099YHQvWzPpL59Z38+wj4av/fR+b0VgWk8XX9Uvqgq2bx711k9jjutPKk1UT50lqe+fxBlPYIOITzu08Y+OuEwvkbteD5aZ1Q9DJk5vniLOr5z3Vq9uQepvg75Rr1s2vC+qDWEvugJir8wS5o+eGw9PSXCrr4dGUM+74nhPWE3xz31bs0+AmciPfA3K76uVKm+2QXovWMzq76GRo4+q2GePd63Mj+9vvs+go2Hvms3iL6djPo9jr9rvmPEobwKPpU+6ZXYvsGshT3nU8g8Anncvck0Jj1y1qE+","aPSCvnD2GD7IlJY+B2nLvV/eF73E0Ao7hIV9PvTrNb3ZpEq+RlSMvemOi74LHyc+INpGvkmwYz5VfqM+gWBLPH/kC79z230+aQAPv/eq0z7wqMO9OaN+ujmhm73IcNg+pIovv4TFaDuusQi+etNoPrHQQr58EaG+Z5ypvt9AiL7ezs8+QTdUvk9/I76vTCo/wyo7v8FhUT329wC9DDCdvp+rfz7v6WA84vAHPhaRET9Qf6G9q5MTPRW6mr1KQbM+zvBMvtwnCD/KrWa+riDXvgWYIL9rlH6/EOCXvpck+z6CJti7EVGUPuGnIz/KkN8+XGhCPmvoxD7aJUW9UhJPPvGsGr7qIYe+lFnCvXKSEb499bQ8Sl2bPUC6+T3/C8c+10zoPB5Rw7xGBPs7X6Fhvox9vL0MAM49PAsDvtgWkj4aBye+u3DuPsoWM74F8Oo9Cr6qPkb7Hj6cQGe9heeDvixvzj7WQhA+R0kYPlvdqT4xyuM8Fl3WvqrErDzBeIg/jKXVPtw3677+mV4+3V8qveEpmT7WvjE/lqeYPjzNNDxvDOs9IyexvZxYvL6LkSM+NF6xvbjJFz/LYQ++N3avuiWwo76Bh0m+bpjAPq9LLL4VJ8S+94sCv2GIK7/Ta6E8c7tivygyfD7lqDY/29XzvvbMiT3iz9y9z7qcvuNKUb1VPj0+/BTMvjAcNr1Wpdo8jgoBv/7OA7++3yM/7H0PviYJkL6iXPs9VTp3vYwSU79dzCy/zglkvWRC2z3XCJq+QQajvqRmor87ugC/WmtbP5w1W76rzQs+0lzmvQoarD6gIG29Kl7NPWoXMz5cT1M/UOXAvlUapb5EGsk+Meh4vjDkib4Xga4+khefv5AdRT6h75e+pggJvxVwB7+ChTk+0bNuvy9tpT1D+Q0+qn+Dvt2w5b6RN/o9QyZ2vzAiKr/VSYC+cF0lvpM1a739XQY/w3WUPY/S7741/Ee+4gtMvhd2iL7IuEQ/dfZXPrpvsj6+u9u+akXaPucctr5LFvu+","Rg2OvsQVlb6VXAA9RpbHvqkpf71Raeu+VniCvkAu/b1id2g+y85RveUWNb7twde8cMFMOQpMK76syNs9dg8qPtJ8A78K3Qq/5OZiPqkOBL94+SC+Xt0TvdoYIr5nBMY+JZcMvWCNvL4yTeO+2LzgO/JLFr6YM1k+nXEqvhvtMj23twm6cSi6vNXORL73PlS/2AERPftaDL4VVRE+AfhnPtjUWL0IdiO+ZqDCPmVLu70LQy6+yf47Poicpz43Flo+95KLPW9NP754Z7o+TndOPiC8WD5Wtru+sr0bvrUEe76DvZw+3ac2PXygMj5O3N2+2R+uPc74BL7L/es+AKjpvqNJUz5+gig+e9jDPSeV7r6MSbS+fLe8PAdQCD+AS5++nPeQvY2oXT/Endw888G9vuk7kz6tRUA9VedRvoXVSj8yrRW+4RQkPouL3z4pKqM+CDN5Ppvl5b1UENs+5OjJPr2au77C/Es9VOi7PorMoL7lUIc+Fckrvmc8NT7OkmK+VI0Pvm6vmr4J+GI9+4TVPlkrLz/y4XY/hKWJvo+KJr9DSYe9u5uFvobmcb4JkiU+I9qGPfjpTr6K/te9sFa9PnDfmjueMnG+AOlhvQxcg74yczi+7dzruzW1+z43FLI9tZbSvmYJvb3oMR6+b40NP0CAuL6BDRM9YNsOv/mNvz7jaZc+CHNfPpLcQ74XLr4+LGABPk0M2r1B9cg+XiMIPryJQr7Tr5C7QC6DvxfrND4RF+e9wCiavhNN2T2pje6+xbMTPpE3GD/6QqA+HuEEvu+4h7yFIgG+pChOvrRaBj1SC96+oueRvY8ijb6PRr89Iv2YPXFnDr6I/Nw8tMsMP8KQxD7lMHW9CHBUvVNIwb3xmx+/TvnLP2gCGb/aZAW/CVVAv5OmzTxMYBi+hqIFPcnfmT55nCq//AcVvj1tvz0P9Fg/lokFvojXDT4JTTc/2X0Av4v7mD77h5w+d0EAvhqMdb4198m+Jye7vXzSmD4QBYI+FjtIPkOEgz4jDi4+","kfXivvZsbD54Now+xP7tvtnJ/LzRnD6/TTVtP8mjpL6HOZM9vCkqvpFoB7+8FfK+r7QTv/SUqz6QcOy+3Ml4vslWJ7/cCzK/iPyQvQ6I4T7juT8/U9rkPbwcHT2esa++DHGbvnSVgz4e6gS9rC0UvpuZ0r6NxD6+OO/EPmggWz1ToZC9txCivh00br8lqv+9RMIIvgHIMr+rpw2/JbnlvXj9ij/kOqU/WslcPgueFT1m9i4/uUUNPoYGBr5J1yI/OPuePWQydD5eEMw9kIGKPqNrGT5LK+O+rSYHv9Z+Nb+SBmW+2zR5P3rnUz89oq6+F/IVv8GT2b0fDa++kHgrvw=="],"recurrent_weights":["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"],"bias":["EFrLPcbYTj5TnNA9YYyevXbLID76Rw4+n/oDPmirUz6yfKo9D+lrPhV1Dj4iEWs+6fTFPQjW/D0WWkE+RSCHPaUSYz5PP4c+AClrPmoOKz57HQw++MF4PdokhT6j1GI+G+ERPhnl8T0GBQM+Cw40Pv+G+T0OwAw+18jPPXyWVD0luJw+pUR8PWWhcD6pV5U959ORPnRpQT2OjZg97SpAPrF7Rj1YF0G67q0OPrLm+z6mqqM9Th/nPctb4T3xVaA95SETPrf65j1F+TE+z+dqPstXjz3ShfY9oL1lPq3OhD0rapM+1x8RPk4lPz7ezVQ9Di3XPhSVqTwLnpM93FhkPjqKgT8U3WY/oEqdP2VrfD9iAoA/ot6AP2lWeT9v84M/0lOOPx+hez+KzHo/Y/d1PwSUhz8jhmw/mz5wP5GFhT8ci2k/KWqNPyYvgz8Gwog/3I2FP6cYhD94xHg/t+CNP3p6hD/y5og/tl2FP+9OiD8CkoA/gmeCPwtdgz9Z+Is/D6J4P7gBjj+kx48/iGGZPybxhj+03ok/wJaFP+4vgT9/NIk/aQ6IP9Qqdj/C5IM/H1WDPwvZej8tuoQ/gL2NP2WwlT+9gJI/3Sh1P4Rwhj+oe4w/bdCMP+w4hz+qDpQ/9xiAPx5khz/o/3I/soaEP1gieD9WrIc/iIVzPx1/iD89FrA9UsUIPrkzYj0/Zq+9FT1kvW9Qfb2OHbK9EbK1vY/aUj1JbcU9kMPwutOQ4r0BA+89GSEsPQM36L2+YvW7KW7/uQeHCz3LatY9u1+zvGP9DTwDsmO8pli+PWJSYD1T9kO9fuBpvXciFz7qFbo9vIhpvRter70bwNK8pIufvRtOlz2VP5M9wcRkPQSRHj05c5s9/qIlPdTqbT2z/PE8iV82vKhGdj2Ajeu9R3MDvqU1g7wWC8K8n7qjvK3KMr2HYCU8T9kIvrXAh70lWrU8JYSPvSwjyD2N1hY9iFB6PTqtir0Osv887S+GvQPIiT1KiVy9Qvq7vAFE8Lyb77y7","9s9NPiMCCj4qeU89OhrkPNs7CT6JTuA9+SE1PkwXxD3w5VE+JjyjPauMhj4NKPE9vtCRPe4aWT3yKx8+gx2dOnt/LT6gTq090TgzvOXkSj2BbwQ9zMnAPVsuJz7/nm0874aNPlQRTz50djU+ddoCPv/MvT2aUoo9KyuWPso7tbwnn7E+3oM5vXHTnD0XZr+6ZsuXPLb8hT3H5x0+QeoyPhjZND7yeQg9d8RvPgAXcT1Sj9494HBaPttI/zyZcL09eMhhPj5Dhz2slFQ+PBgtPtbIQj1CepI95Qv2PZRHNT1wgKM+kIljPV3PJz68IAQ7UfbRPd68tT1KTQo9fUDkPQ=="]},"lstm_3":{"weights":["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"],"recurrent_weights":["6zVzvdsKJT32HFo9bNq1PuO3BD73K/G9s2pHvWX7l714sx2+GPAQvpxerTwlqhI9Q3sVvSkIzr6snuc8xhKuvoTWtj15CQg+VI08Pp9lMDxsXaO9PT4CPhmx/bxblAI+Z1/1PXmdiz3300q/ubaMPMwoDr0mo0W+Lz4lvhQejD4rGGo9xhGEPntMG71FvLi9hOebvTHqkj2LJam+yGcsv8KLiL3B2mI9FtP1vb7dLj602rA+9awsPk8Ohz2LhZW8BlYHPajahz1U/DE+Ag1yvs1pOT21HJm+xZcoPj54MD60OIa+jooyPn0nBD4eMgY/332jPrZjKz1byMC9JWhgPdIg1T7eDkm+5vg5vvILlr3NWWI9ub+8O9KNLL79e9i+AekhvYx5Dz6cbjE++EW1vluxXr5XHWU+gdEhv7iSgj56g6I8BpxnPmwogT1zGYU+8VVHPdg42T4sJOy93H1kPAwfr76AaI+9JdYOP6TTQb6ePcM9PpLbvZq5Rr5vcIm+JSKFvFOW5b2BW6W9ZscHvwxwlTzD15A+S3i/PsY5OT5lrMy9s676PTHDf72xtwS+MLTovhlhvL9Wra2+R5UuvTZyQ747r0i+fBbPvdnAfT4RRxm+FPpbPX24MT1zP5O+3+cZPaD/+737lZu+hZcLvz6zqb0kmji++HeLPi+c3b2biVq+ThxnPB3XTD6C3Ka8HBEnvcdFZz22lG++pZD1vOaEEL6n41a+9PJyvlqiVT0SMD2+WYx1vn0wxj6q+Tw9AzH6vFrsYT6zBoO+9JgnPh37j72PaqY+5VsDvpMaRD6sxJK+8d2lvvduxj57Vzg+wCwSvVoF8L06Txw+7x4WvZxSu70p9929ncOWPkEP8Ty/Izc+or2hPjT3Wr6DUVu+6bcnvjhK6D2UWym+0mZ1vExwBL/TKHM9EJpovcOmgr2UIHg+DTYXvgE1jzw6w5S+Hak1vogJmz6oLkm+QlIVvT1Xtr33GiW++JGqPvYDcD+l+vI9LXujPjObZb4ptpC9","CJbGvhUC2r5Hdg0/SiIgvo8+fL6mUGu+lPYPP4VckD1UYEU8fPNuPst9mL1mb4+++ZsJP+yier7H/Dq9xJGTPptpir9aOoE97FrOPT3nx75hdwI87eVYPtOjY7x1Zhc+lJKIP6+DI713ub099acZvjhIjr+7Ohw+Ikh8vsScJj5XiGG+OxUwPhiTjj6HXUY/9dHGPKbBJL1Vin4+8gaCvjKorT0+3QY+bsbjPoRyKD67p92+iiMZvill+T7dBmM/6G8Yv91Vrb6RfRc+p6tXvlJRdD5Y5Ye8f47OvfKUYL7hywy/8alcP0LzDr6YKzW+IjZzvo1FYTxq+tM+nXDuvlC75r09LJ491D+IPgHXjL/xNDw93XDTvdjXjT2EksG9p5eJvoGCFL8QVH++XOF7vrVmSbzSS447ykkhvqutFb0oLKo+dGO8vvxDnjwRfie+8eW1PXN+Dr7MB8m+KysJvul71DzTNve+8tPJPtw+Cb24CMW9tnKJPjsjvL7Emoq+sfapvWzgXzwUkte9SIx5veWC4jxMW4W/REWfvkvmWD8HE+O+AIF2vkauhD72edq+jF6NPbBWRr3UUiO+B/qhvUHCnjxhe/U9vUlHvThFBb/m47I80dMXP7h5Bb8GeT2+4iNqvsJ+Xb7GIo08jTnnvn8bEb0n+Uw+ecD1vrmjNL6Nrcw+TAolvgjpBD7jwgq/ylTuPrd3rL4Buwu9oxqnPcnG4T6+Y9c+pAkPvqcC+r0DwB4+WFtcvcXR3rsz3Pi+a+zPvZ+LGb4/pzW8qjYbPnnUqjuxS70+6mKePkJeSz2DrWC929n7PW0/Tb6A+J69A7mRPs+lRr1K1gE/d+lOPYYtgz4Bum+8cvf+vuRKQT4I07W9I3Q6Pg+aob4GNHE9W6EGv8cznr7BGpG+6XQ+Pp963j1x5GW86h0UPKriCb/Mv0o/eQVvvqHEuz759wG+qFpBvtAhBL7Py7i+wbSIPK5+oz2KspY9GzCXPTqyzz7MvgK+2TdEvul4vL6VFUk+","8TaGvoG6tj6UZCY/IrfovW1Xbz5cHFa+4LE9u1Zrb71NsSs+SfhiOyvM8D1VAEk9eI5TPhRb+z3xETE+EGyOvGgCk777t2e9VRpgPYP8Pb4th9O9doajPrRXIT6EZeO9Cn6GvphzAT2TSGG+1aExPpG3HD7QZj09vir1PTOtvDxRL4g9HbQqPosKRr4k9Rm+qc+7PK9/YL2PMXY/eiioPhS86T1GxjY/HHMBviIWX76u+ZA+h4qjvrbE6b16eco9Gx7hva+uU76MjYo+ufkevm+a1T1PHIY+rQjKPr62gL35O3S+YyNXvq5TqL1frM6+Y2NQPcn+WT6H7jO+N5ejPTBWTz7x3Pi9yn+Bvna7Mz0J1KK/nt5WPnF/5L6jpQE9XbOLPq6b3D61vB6+bYIWP/TDdT/icEG/8GRIvtE4h71Va34/910Fv17DJD8FbDq+l8IPv/apSD4K4oq+u1pRv+YVET7WZPG9oj8tP4MGLz6C0ok/5B2pPruAor1bxYE+oXBNvgW5N79+viw+dE/hvuGpMD8hvcy+HSKCPidDiz77bzg+GxF1vsYohr9W1aU+f4pJPkceoT40fNM+85KKPm67g76nTLo+6JTQPXH0ib41j2e/yraAO54r0Lxo1C+/5W2rvZldrL6zhvS9WXOlvrFlQj+6C+e+GfQrv9Mqgb+UxIe9Kc24vLBWd7z8++G9BY+MvGNp1T0AMvu7i7IMPztkVz63pFw+h7kAPokswT6CkoY+Y35aPjmFI79sWkU9udBQOZYrrj4uBSi+JoUaPvA6X77Aoak+IxmgPkq1tL4MuLQ9YkOLPCv/9T79ly8+xFhePZGvWjzl6Y4+79YwPs+ySb2LCim+2tn4vOuWb778pBA+Bt6GPC54tj5Bgp89UTIXPAaRoT1G2/s9+Js4PKWArT05wcY9cPY9vNIaNT5zA3E9KXcCPp46bj7bN12+w+7bujM7Tr7UOdW81rtVvalz3z2jfIw+is9Hve8uWD11Z8w7utzIvVWLdb6zesE+","GgxcvBLd8r1TqxI+9ZWFvcD2oTtFmnc9JywCvr95+b1bhgY9rRNzvRTovTu1SWA94P+5vc52Zz61rEA8tGmCPdX/tD5aMXm+ng2NPU749T3j4LS8b5WAPkrNOb2P1VA+/pMtP43c07xtvQM90t9ivXZYmrylx3A+g8b3uxjh67zBoXK+4rpKPTZHWzx5ZVO84KGmPsYFQTwh3Wc+5alJPaI7QT1Yvwo9DWyZvaF+u72Jequ9AW+ePj1hjzx+kZu9vkowPjsJ6j4eWAe+QLPtvU8vTL57pyK+kHl+PgGO5T1hvy89BrsfPsGE5r0KbsC9oqltu2FE9z7MbtK8ddQ3PdUshTwcurY93Iycvk07C7rjQpe6Py4DPmnSSbyaIbw7e4HMPWFmYr3pif28e6QbPm9+z70heb+9R7lCvSXVkT2IryY+BrSjvNTv1b3uNBa+/p0UvsncPb7Hwgk9MmUgPlmO5T6muSo+BdQAPj5Dhr25k4U+8Ko7PmI7fj6QPt09VcAwvlIHBr6TsLW9cPfIPHyDu71UL3e+SBFJPp8Fmj2Jclw9Y7XIvl9KT73ethG+XhVfPHOrCD8TC00+XR1+PXvmG71lU0w+lZtyPv9PaT0WlDe9EH7BPhcl0LwPjNi9ZIsyv5iThr1DVa+9kzu9u4aAsr53mMW+l7cnvvrZj72g9I09GcwNP+NyJD8vV5w9DztgPz9mRz2M6G09QKOGvepzqb0nEv2+MleEPXw+eD4Yoqg+lIpevvgvgr6IHoe9366bvFB/nTwSsm++lausPoQozL0KyDQ+ZeU2P/4+Wj4oHMs8vZa0vPKhzz6nQTE/9lr1PXzEbb5qvec9SceVPULYFL/scfA+Z58bPovvCz6VydQ+7P2LPmlt5r5TBsY+B8PFPugxsTskvL69ObFUvhz+TT/Tvic/YFVFvKaaxL632X8/q/uqPb/dHz+7d1Q/aHeQvgYq970LoJo8i0MGP0Gkmz7MHXI+NLouPxtAKr8La48+Wwfau4oQ6L0dyeq+","TWFSvl0BJT6sCxG/jfAfv5VhLr6hmpM+v3dsPhvhW7524eE9QS/5PU5mAT7Lfji88jgVvbzjqjvZhoW+XNFVvr8Nkj7f6PI8A4AHvFRGJb5Ki+k8wF++vVkPtL0YoYK+XWujvWLb+73W2nW9jNRuvlq4L76fO1E9u8a3vTcf2D2NgO694sdJvg+65z2ZLyk/szzXvfp4Sr/zN6o94go8v7DkDT+DKjS+GATavHgLFz03o0W+Sm2dvS92sTyn/gY+aEfCPRrEy7gGgJi+9lIWPujdh70IuMi+MKEBPjhOab7uiCu9AH4+vjVy277nzfk+alTNvohkJz4ctfc9DoMYvEGl8T43/fW9c35JPSytgj7LbZQ+DSFsvlnVpD1f1us73UtfviN6272DxUM+xlm+vnleaL4tRIa+AAJNvVS1lr7MsaS+Vd5lvmQFnb7Df7q9XVuTPpgOA76GO+E+1jYFvgDMv71ee14+N5LLvvbo1r34LjE+iQfkvAmX5jwnMwG+MSpCPIooMb0cUwm/phGRvSJVgz1xD468ymUCPx+BZL5iPn6+dz7zPTtHn73Gn/y9y2gOvvphDr7Sruo8UQl2PmZrKT4WR9e+6E7cvdbomr5ttpO9rrinPtFmyz0jtQw9mcBHvsuKhL1WgdY+9Gy9Pl3Acjwu/ju+auw0Plakwb5PDw8+RuH6vsxus70KXFI+lRDHPajvnb03WHg+I7dJvv4s3r52TaU9ipwKP+4jaL690O4+8lTkPRp7O75mkgy9jx7Dve8aZj4WT4W+f2lAvnHk477h02a+X1uQPjTGSj29jbU9tyVZPtAecz1/Ym++nUvFvaFTMb4K3t+93HO4vi8MBL2LLnW86WOGPlSDPb0YPMm9qKO1vQCfm757v+s9i5NoPmL4nD2Cesq9WXiOvssYQz7j9LM+wipgPiHngT4lJpo+XrezvQG9T73hSsu8vBxmPT3EBr6b6py+fgalvfWpjL40sO2+gLU2vltOej2evdg9Znu+vbeobb4Z2Dg+","KWswvktrWL0MPNy+Qfo2vq1n4T5bN2s+4yPZvmo/qbwUed09AcXtvSKzkz78z9O+D46kPtiLEz2YSbO9a9xCPqE/vz2cIJq+5z5AP4NlMT9vKyc+VJizvnxK3D7820e/wgRCP/9YOD+z7LQ+WacYvuMOwL41u0o/JI7cPnxJID+Wkik9OVY1voqxC785YyK+8LaHP8R7lr/kkom7SZLOvk8Tx77/0ea+vXC7vhCi2r5uRJy+I2TNvp9gtD5ztJO8mX0hvldw0z6Cf7y9uRvcPqWA9D0RtVA+RzLwvp14Ib7KOOW+3+vOvdSy173/GR0+G105vmyyxD5tccw+bbt8PXmaob07A3++Dfgqvjvuvr7eHxs94FGbuzn4tj00+Tc/3tMfPrOSmD6WOhc+U2WEPSyNvj1sC8m9Eet8P/5V775T4SS+817jvfoqA74THZG+zWQ5vk+3Xb2rKCE+HWevPqdcpTyn/Lw+XqCgvni8ID7iIcI9DbxtPvloYz7HwBg+LfKOPfFaMD6haYG+15GQvfQoeb4Ld+Y9rj+mvcLT2L6HiUK9EHkUvnFiXL75S24+Vm+NPvE2dj3OskG+5yNoPoPTSr4PYnm9NC5TOv1aOD6D7RK+m6uFvoBYk73VVhI+MeyCvoAI6T670li+XwyFvZuykD25mSG+xVkpPjGQKLxvTRi92brVPo1yCT/rcpi+D1kxPdFwlTyOMkW+hPhiPoVuoL6mf5c94HQdvY8FITttMJI9CDAJvu3Jir7MjDk+N/IIvVJP7L1NvxY+QovFPJlxyz4IXzo/9367vrzLmTz0oRY/LBuFvsdomjt//vA+WJL7PTBiDrxS3MA8zrN0vcC0JL5U3wE9nx6QPZuypr5QWQ0/3laAvRSSBD1VlZM9BxkfPv50tj7N2m8+Ua/zvQe4Mb6bLeq+LjzLvigSkD185UC+jwSePvGT6r6dfB0+ztWuvtcW6T4469E9Z9vXvh1aLT9nli0+bhz2PeGwH77Rmq28Kg/ePskrjr38lM49","DcUIvtvil72G4l+93BtMvqFgrj3lshw/gzd/PfruJT67gv0+uMXBPDFs2T2V+UY8tTHCvvB/+7x7BeU9fI16u/f2lb0Jvx2+ogynPdrWoL7F27c9t60NvQ1QD76UDXg/KZFQPt6J7b2evco+kucyvfb+AL3/YCk+FLVtPoyoE70UTJa9YKiXPnbbmr2YxN696YGVvmVpEr5DI5i9k0lSvhiLfr4LGbu+e2RRvpcVrz3wa+O8umpPP5FXET6mTAK8nrVAPhB2KT38joS+CMyJvifGZr6Pyye+8PUBPuStGr5AbIG+HPcWPadFlr3w6Yk9HL+SvD+/PL40ul29us+JPkuqJL3ERwe+Hl8iPjuI6TxqyOc96jtdv/vEpb2Ng68+757MvVXzxL6vYTI/PmIyPuxWcL6hYAU+0nIFv8VvZL4Fp9C+8NDXvTF3AD8uTMu9Fr+rvtV5hD6oTi0/ICBtPaEXVb+0RgG+3nMkP/912j5emiE9Cdr8vbYMy77yIRQ+ZJy+vtWADD4xyci859ByPspAwj2XWv+9VbYyvteM4rzIPA6+nX8HvtUrcD4MHgm9tBKvPoS7/j3kVlu+P0/VvnnFLz/os/C+de+aPomOyr43koC+pjIVv2g5PD5o2YY+FAp+v3eehj55nSE+lV4Mv1bCMD5q4Iy+l8TPPkKd4L4i77S9Rkc6vmmOOL6Fjj89gY10PZCvt77idrG9r+Bavo77TT3Vejg+RJEDvre+Oz7W6Jq+8EmovNPNS798The+WJzVPVZwYL3vFXy+Qfk4vRBHRb6oRyS9tGBTPZbt47zNQ3E+T4zwvj+Wobyc8jQ+mhwVPfdRpT3Fs328+XwSvvP/AD4qQZ+9VSwFP6egeL+ZcFK8UfSOvZV0Qz1PLcs+QH4kPmyr0D1iIcI8wYy8vVdlk70D5l++iFJePVJa8D7mWkC++NPIvdV2Dr6pdO+8imodvZH4Hb5Flrk9mjyGvf9aED5jwSM+8SDuPUZ/BL5d+cQ9XfKzPM6twT43hJs+","aJYYPdpuXL3Vr8c9nvkhPsuv/D3NV2+9YdKIPAThWrzP4V2+0EvXPJ2Spb1/5jA+yB/ePVPexL0bYKG9QDsevXfiir0ArO69fpNvPrjECT4Ehi49jVyhvccl+jx/dkc+l50OPgv4473WecI8ImmzvEwTAr27cVw8W8obPrCmfz22M6e+EEBdPrCdkb0MrsI+CPN7vnkqm70V85K+TvElvscAqz0ccPw8QOjXvKhXrb2B708+mILTPXDlGz42+869WlNGvgPv7L5Q5ws95e04vq6hxr0lRRk+yp0svMST7b2c7gK+pUcnPi26YjybrTI+2yGDPisAVT6GIVG+TbszPtL/Lb1MlPy9mdO4vqOkoT5+Yq6+2yScPPtdij6Wzpc+B6uRPvOXvT2SOAo+GCuDvXcQ5T5TfCu+04dQvCBMMb6M0J28rsDBvoWseL5fao69Caugu6dXiD1IM8Q+zTJ+vs7XG7w1YwM8RaqTvZYTXD6pHvG9/MfgvVwpGL6hzCE+RyCovmdKRL7BIOM9187LPdqikT597MM9oak4vo/woz1vty0+3C6MPj4Ej71Cn8a+ggI5PehHNz6Ood09/fEiPQCWwz1b9TW+0DtSPhFWcL5gc0Q+z/gKP2izqLvvNW68kwTqvHi3gb4+5k69dZ6TvuHlsbxD6x4+c+HbvASGY74h1cE9dBdoPuqkuT5qyP088CCXPkMWdT7XEs+92XSZviGQ3T5IkRM9j3yRPSQ4jryvxHq+dCslPiTB8L55q6W+sAQsPm8oH7ysuzO/mnlBvcVbUz+CQMQ9QgeHvYHZ2r1C4Yg+5Jewvu4ArL5v8FM+MoluPakHEj7L7RO+IGTTvYNRkL6GEFU+LabWvX+Mkr7P9Im9kISIPd7yIb+g/Ag+/OwHvrfB0r7re5K9/mpMvpdp5j0970A95ZQWvusM0L4cIok+vJ1rPhuzE7xpODA/LWmkvr4oej3QY6a+Ez4rP+KLGz5MY/g9JrLsPv2qLb8rv4K9m3g8Pq21rb4k7EE+","aYxVvR+sLD5l9tC+ZImCvp9Anz1BwYk+hmYevr7+u73VE1U+WO7wvUpDIz7EXVE+yyDcuyHrn7xqpAK/l4puvvAToz2NXSu+78uCvq8IAz6JrCI/2/J3PtQAwD6Fn3W+4ydCvvHwKz4pULQ7IBiLPhfloT6XC4O+hiqfPWNH3D27saK9gM+kve1mu74rFFc/J8kkvQ6GeD4SYIe8FXBbPmMVpD1aIuI9EfpbPlR5EL2XSLY+nQmrvPPJSL5YcYo+DwTaPf3kXL5a5FY+BhcUPRZ/WD6zkTS/GYbhvTpCVz7sDgS/cDEovmO85b2fsry9r+TNPo0Mo75lKP883IUVO7wPW7+TRse9qmcDvtiOUD6xhba9Lfpqviz0ar0a+LY+a4wfvsC8hj5VBRk+6GU5Pmistj4i90u+xCOtviMVBr6ppFW9RFwQvaUHvz5secS+d0WxvvnbHT+uZMW+pl1PPvwilD6DD4A+Gp4AvuhtLz/dnkk+AyJWPrb2Mjx/4g8+G3bmPq3f5T1hGq08EtjZvVfToj5XUZA+a8+hPky3qD4/R7u+/l8avmFWVb132Fe8i3pxvhD0Cr4t4LW9TtudvWLFY76QXow+TCOIPsGzi7yFPES+4LSbPe0yXj4fDyq+W6v0PhnNdTtN4e29wSG4PJwuDD6XzVU+s+mRvki+UT5bUwg8CWQWvdQRyL2R5RG+vuC2PlOnoL1Eu5o8arRaviYjkb01+de9ikAKPpJmPj1a1U8+KI3IPVvQhD3dIXi8s+7kPXXSoT7rgz0+rF6EvkMTwz6JYgC+A1rCvq1I8b4Uxik+bNjJPfBaAb1bErG+gEoQvcxcKj9Es4K902UNPz/ZHL6tgqs+V0aVvVtIkbwl4p++HNPlPcG55r1ey+i9ClNyPTu63L59KkQ+o1NjuvgFLr3UF4I+e7CXPdXxOb5oEqw8hz+bPsrHBD4XQcs+9fM4vlzSMr0txdA6kdecvcBnkL04qY2+T42DvsKh1T1NeV4+vah6vt38M7wepXc+","VVRIvuXyFb7kpTc+p01uP8gzvz5P1UM/pGlKv+Yb9j4YUEQ+Swx8PR8GnD+ACaU+7VewPn535L6KOgS+vPvSvuinpD4/MUm+8dBrPkdsF7+VkZQ+Zxp7P7WV+b7oQ9s+eKEgv6x3ez4sCJY98cuyPiEmgz44JUw+11cqP1qjjL7G40e/tiLnPqhbFb0wU209jcSePbXbEz6WV54+Ts3CPoBCBb42roW+TC+yvsMUXD2FBKk//xDvvR4VuD1aF1W+/z+DP3QqAj5j96k+L95VP00f7rsIozC+P+wNPgXYRT4fvGC+A52lvgDxuL7vj5O9Kv4TPzgmRT4k0As+2wi/Pmfpkr1TXcA8HI8yPnqtRL3+4jK+dem+PRTwyT3YmgC/n/fHOtfVPr7UFRi+lY/BvsRtIrz2J8S9MiFQv7vlHT4A+dy9HYbOvbVpPr48vb++paYAvy4HB74x1pu+I344vnhKhbzxpkG+WmXFPCf7ib6IJse9zCSQPhNf2LsD+o6+jr/Svs4gmLu2lt+8eIVPP39oAT2iyJi+ykftvUbnsjuHXLg99akgvXY9CzwFRFm9v8oPvr3IMz08HBa+QD6jvrr0ej3ZLAI+B8ymvljUNL2UMzM920zVPkAN1b0yLcK9X6YnPZcUib4+5zG+c3Y0vhlRQT1zTQs+xMyovcdGp77y9jG9MgiZvgzKW789bkW9UCQ/PfjhRT6tIVq8ANA7vXqUkz2kcZi92QMtvGK1Rj6Ddhq9lcsRvEAKGT2W9SG9Nv4LvlN6pT7vRqk9/kMSPgvUH74/Zo69jrQ6vdojfL5abQ6/k95vvB0Hnb3Zi78+OfvLPfWaq778Als+3SQkPf8Oq7zyYOe+CVvvvZ1iZD7X/pC/a/t2Ps/PhDxugQg/22oHPcEVMr96oSK+lmNZvR6ouj6wun8+SmqSPHsiUT4UuUI9YAY1vKk0Oz5iW2q+cxlmPhzrLT502ay9Lj8Zvap2UL4I5Tk81JwbvdaQ6DyxZ7e8rUHTvr1uczz1u4a9","Gq6BPjTEDT5xWTq+aUcQPcIyJbz/3OE8bmLLPO1scj3cRzm98KJvPWikury9tWI8rD6BvtCwuT21dCa94QqSPWUEVL4EmW4+CEdSPj3chL7pamg9c7YAPusTBDznqem+DbqrvndWDj2dvyc+ZD9IvRhyDb1HtXq+X5EJPA5Uvj1bLFc+3LjRvbDxaL5F6bU96dG6PtXyAL2k38o9Bx+ou4Jt4jznrBs/ZFiwvcWDqD2e8kw+dYEIvcScjr4qG/o8k3yOPS9sLr481Mo9xqx4vQX9ur2q+eq9RHaovuGHLz5r+po+hclRPO74Qr6qvio+WOAiPhg0gz4OqqY9Qh3WPZjX3j7jS0a+cvsmvGvb7b0wgja+S6HAPtZsTr7e9G69/A5mvprRg7+1Dnm+s9sCv8AUi73+oBq+04LnPDRptrwg8aE+Kx2oPWW6lr42A6o9C4miPl1/3b4ocEG+0Fa6vRijij6Vg+q9xIaDv/1xcr+hgWq+aGa9Pid9CD/NFgu9LiZlP1S6AL7gpEi+V1YGPnnajT0SX5e+yLbqu5eE1L6B5kG+hyPNvR6Sgj1EeN+8QROhPnzodT5ech6+N9oVvw5uRr9gWuU84oS8vSwLTb939rQ9fNklviv8PD4gdzq+Xwg4vvH+dr0Zg4S+/wMnvy4Qar1QBaq87ihou7k25z5vgi4+vb+cPegscr7/+fc9wGJjPgKQID9ph4g+3sXhvUSWaL4cVlY9ubSqPtOVHT3F1DI+9ZhqPY3GPb77lAG+3JldveYxhD35+IG91FVXPaxlwr2PDAI/IsbSPXsXQzs5hYM+aTmyvI8/YjxsptQ+SxAwPlBmdj5MtgY/t3xbvlC6bL4HD5M9oVQuP8janr0RbyY+xF1Pvc1eFz+4k8y+TXq3u1HY7b3WDaG9vE4dPsMdmj59XlY+d6k6PksxUT59TDa9Wy8/PvGjSz4YVHk9ROBPPDf4Vb67IOi9RWvjOy8Ox72frgc/cYd0vr2N8L3vx2O9fzLDPlrbzb6+u6O+","rNYcvtf3jz0ucFO9Zfdovmdwi70J7lk+NjRlvhoeqr3Bhhq93NYsvRa5gD2q4a0+rnwgP3XjsT6lPqs8JBGWPkEdzT73cgo9pJpzPqXvX75HXYa9Rjw+vgt7nL71Dw09kqYTPoaugr4BqXc+m48EPq2vD75YbUC+h4BzPgZOEz2XM/O9Ox0ZPvkwg76/u8S+fbJ7PQrI4b6cbss+vwl9vzLFib1gSLY+rrDFvnwoSz213ZW+kAqQPfeDeT1KDdY76wiJvks9Xr6Q+iI+1sSIvgZxGz7FPEA+Vrrfvrb74D4aYLu+rsSyPhe+lL2fgOM+26RyPg4T0L3+JCg+mcOpvTerIT6/OUK+X8lxP6qy5z3xDby+vUS8usCbxTwnmGK9Ks+gPoZkDD6BrlY8CfqdvVLkCj7TLkQ9l76vvZN8cj38D/s9saxYvmbkIz6ZZa0+ur19PvwLQz4V0a2+pn0lvn073j0vTYm+sqq5PjdAj76vSYs+mghLvu9f6L0mpUE9LWraPUe+3r2o+Jk+sWPHPYrSmrwUnQa/HXMvvWtytz63EFs8UJ+FvnT7aj4m9DM+UHZXvcb5kz0dQ4k+d96/verKhD779169c4+cOq/vjr196E89c/zOvQmvBb5G7he+pjCMPSEmq75y7Yq9SgZvvUPFD77Wh4M9lvOBPtMFz77TwD8/ICjLPsy55j4HnqG7/FhoPqzhXb6NsDo++P7YvmaT3b5XUfI+m+ssvq/YyL3JvxC9g+1jP/VEQT6mrCi9eSV2PRLZcjw/LFW/IhyfPvNk0Tw1nJk9/nA7Pqv8tr6RdRc9UpRyvhnCgD5Yoqi+eTAGvt8IIT8l9Ak/XY6vPnn7Ab7PY9o+LybIPVP4x74POdU+2pWcvRwHsD7AOCI/NDYcPrV7iT+UWLE+IzqnvnWHjz7p1NM+oL5Av0/Jh76e7To9hb6Cvby6GT9Ookk/fs0EP7JqXD5P4Pm+TaNivQfFOr3Nf2o+48+XP3mZqT2sHp0+icicPmk1Lj475Ri8","Vig2vu9BZj7kXqS9zE1gvkQZhL2p9Bu+87clPQDgq74Xdhw+CwyzPazLMr4j4yy9nOp+vhnFEr49wBu/wAdAvGZWSb0A0iG+0yrdvJY7Yr7RCua9na0MviRdfT2DaSi+GXOIvYoHer66Wfq9oOiZvHP8mb2fhw898u6MPW3j070to9I7Tr4NPpSR5LwLcT2/ZQ8VPuDlbz1FqHs9kpCVPcxKYD3ebhq+9FdSveGxhLw2pJK+0ClBvgBlGr3iFBU+wLQevscCUT05VZY7PfQGvsEIFL4Cm6E97LHlPGHOCD3CN349pWuKvc8APrx/+g89Vm0nvlKAsrzetqQ8CCGdO8tuTj+eJhW+CdCMvpWFRD74Goq9wb2QPekOTz6DKXS+0RA/vAjxVL4rkT4+ubDVvKU/uLwhyWe92vrQPnlSEDsELQM+MyyMvYGvaz2aHSY/G1EIPznVkr7V1is/So9nPjliwLy7pAU+Z0n/OyRscr6228O9AbzYPtuUOryckBM+t5/OvbqHIb4H9g0+nUWCPrvZFj7vTMW931fHvgsZwj4Ah3I7pYsxP6rq9z0elcc9ed+yPp/oBT+eLvA9HkDBvU6vQT+C9DE+FbS5PdGh0b1R2Cg++0Brvq+4Xz2pqxE/VHcLviq4Jz5hXRQ9V0WNvamFWTvxjEa+ocJxvhmJOT5jppi8hjEEvfDkvb37PZ49P6YKPXOUAj4ZAB4+Wt6+vevXwr01oIi9EZykPTFTwL0I10g+v26YO//q5L5anpY+KksrPo2D370T2py90Bs9PuCzh776Ym6+RTxqP2s5QT3KGg2+tVsVPk6e7z7zXGs9u0Z2vFbsXb50a2W8MzDcuySJabzHvwq+xyUEvc1fh757nrK8Vwl4PW3v1Tyrk6a8rqQ0uwnHCr5TJJW+Q22Uvmw8WL38eQ6/JWpzPU2glDz0yoC+YxNWviBGkj4hqmS7CBG5Pi3kwb3Xd6o7s9unvrDXXTyGnM69eY5wvXiRzL2nIjC96HYKPgpPbr6hYt68","8eFBvnTngLxBZ8g9qH+/Pfpwtz4Zw/Y9fXXmvFaio770fHY7kngFP9Fxnz7t+Oc+H4YBvmSE3b7PRjm/vCYMPmeFOz8g5gG9y8M4vzj87Txxr+g+iqT1veHwFT5B1KM+USsvPgPMOr7rQLC+j/MDvj9lZj+MCV6905WfPrhNjj5SZYe+P5UVveJoRj3bEc++SZtSPq55Ez4gFIi+3pbVvfWh873mPnq+uyFUvh3Gi7571Q2/ehQzvrDCUTzeGJI/O59Hv8XsB741lQm9GwgsP1Janr2H9Q86gZ4zvrGvGj9nm0A+eW+hvAGEir41iAc+0M2AvvP2FD3lrYE/y7wsvvYYbr2nAVG+A6JTvkqJ4T3yC1Q9kfnTvW78TT2fuxQ9aaoQPeE7sDxJr5S80jEhP7hEOr6G0n09wqQcPjnUH7/KtDM+z6JRvDErgL5oZII9FvtJPp8zTL2qiy89EjtRvoyanLwvcoW9EcRRvvX6LD7Bw4A+3NxEvnCjTL1HiZM+iIbguIchgr7kBV8+aJq6PpPdyD0dA189mBxNvjLZeTyajTa+hWPqvRDLMj7Km14+94ajvAa+hL2Y3sM9vQojPSfwQjsHDEU+XYSSvDsnoT55w3o+gOiwPiqPeD5gPck9cdexPtPo/bywfNg9WeEKPdbu9zwSKC0+jYj8PYQu/D0Rphe+AbJKPtwumT5lkBU9MHW0PhW2fr5K34O+viE0PhFyXz5pgli/+GqMvkhArz6Z3Us+IlNCvmKTa75QcwG+OOTSvW5QDr82qam8PY6WvoY0gr6JN7K8cRdoPY4o5j5P/5a8m2oPPjprOj4vm9O+dFbEvv9jbD5w9oa81QAaPpHxlr6LppU+aA60vKE4Bj7/oDM+t13FvTwnDj9nwpk+DROgPc6Pr7yaPqm9IoeevQ2EOr3Wj2w+oAQJvTBdXr78sAI+lCKfPULvvT25pEO9VQL6Pa1XMz/ECDY+mGQGvfHjmr1to7O+Ju6OOpbXzT3g7j4+Vxc1PpdD3D3Cz728","IMk/PUbIyb6NOSK+Ivv7PYABJj+2+4s+qgEVvXdkg75HlAI+KsmVvoix7z3SAmI+E209PYPX1D2jg3e+VLqovaj+c7xoSgY/cKi2vdiXXjzCG4s+wYjovbqptT34MIa9CJIVPlcw+z2X+nq+ExIrPqftiT17LR4+7xiHPKB7Pz55Vb883rhaPhi1Rb5vG5W9alkGvlzGbL64Khq9IAPPPGTB8T3imji+Ta8avmmJ1L5sLTM+qqlCuvv5KT5xM14+ZdmAvpM7Bz57CaE9fTWmvvnXUT6NP/S+BtWSvGay1LyKwi2+ZEWAvZJB8r37Qb++T5JFvsi4iL0q1MK+WFy4vaR97z5Boj8+Kwhnvil4Wr79x0k+cWmAvHX6LT8Ys1O+D3JxvhL12DzQgnW+h1vtPhVjrD57KtE9OpjovkKmZ7y9bhq/fSzYvYwGIz9NtD+9+tVMPvINo72RnpI+50bXOjiD5z4595S+wGlxvSYRfz5dYz0/dLvJvqwZcD4HUCi+O7JAv5p6fr7XvmY8RSwTP2UzAT9UdcU+r+AAP2Xjozy0aso9k3nvvVo1trsvgJ88Ppc/vqn1Oz5UzsA++ctpPt+1gD4mVaI9qorWPq++lj6NIcW+tmRjPkrlVj2z9gc/etA3vQ7hOT42vYI+rl77Pbo5SLy3Eqe+GoKzPjhibj4tW4O9GMarvuKXkj4BHE0+aYQSvd1t8z1XBjq+0wOKPLgClb1goB89OZZ6vvEMkD7DNvo97likvrwZCb/obOQ+aoPVvsdWyj0GybU+PNC8u9L9fb6RnhM+2EjNPSwxfr4+5OC97ZWDvXCdYz1PcrI93kJ/vhxJZz7cqeA9q+27PYyEgT3KJpg9HpV2PQuGGr48vVe9G+XaPJQZjb60NhC/VOr8vRO+q70cGbc+KsANvnZx072j4wI+YjONvlH5jb6KIp29UywBviuQKj4L+Ym+6W6/vdkrg74WzxE+qGsuvzYdaj0K/j8+mgoLvug1F75gXlg9LvuZvDvhlz60qYA9","GngBvfVQ0bzA4eK+arIEPl01tr2kkKy9N6/LPr93h77Um1s+eNo7PiCxBT4tG3G+4zSCv8uJ772+D0w+VH5AvgLgHj4pB7k+Dec8vRjETL2Q1+w9w16avEep2z3fJLW+wEf8PIJjzb0vyVs+HKhmPkr9qrs4LWC9RYMAPGqnL749YHM9NwGXvusTNT65b5W9W/4jvuUOubuxLMY+KUf8vZJjrLyQpZ09k9L0PK1TVb78iLw9CZJ/vmNCKrod5Cu+TH1aPpQm/L0X9py+uMlIPjkJG77Kf5K9DEC0Pdvh5r0wPjS9ONHcPrjHSL7afmq9U5bSPc/Mzjth+wM/RrrhPa9wlb2VPAw+GDBlvi3fCr44ybi+G1VAvuHH+D2loLE+SE3evi8liDvx4tm9gw7FPl6mu77I3Ia/EHhgPkPUdbwHRAq/Kn+4vqyeLD4XURY9YX2svEAapD4K+T894pFQPi88Eb5Y+gU+eDqKPXkQUj+QE5q9OWQVvl1T3j4j8DK+TGUAPj8bJb6W8n+9+i7PvI4X+zxDenE+v14Wvq61Ab4LiLo9CmOkPU/Kaj5XY7E+GAsmvcvRqD4fgmG+FkxvPkDwyjroPZO9Oj81vpv35b7/F1S9n+RlPsOlgb1oEDY+TJXLu+y6rT79H/48NzJwPuHjGz5KWQA+8Qt0vuwOR75P/YW+XQpGvQQ5Dz9oDs88nbWCvjPIZD473oA+LEIuPZINOT8Isqq/Nb8zvyAloL35Doc+daPqva++Az8E6uy9bTGnPRChpT0f34a+KTXKPY9u4r7YOlE+g2g2v+bglj7sGI8+lY4fPQth3r3aanM+YT8Cv5j0Zz78wXG/LKraPov5Lj03isq814f4vlbSUT4hk+I9qvzVPcg43L5Tkq4+dVN2PtYvo747mmq+uGswP7Z5qz6Xa509/NWIPkfShL+vZck9R47fvWadJr0/i7I9LWpQPn6t1r6i+C4+u8EDvuEtwD7AKPs+DFERP8TX977ZHfU9NQuFPqiQLr5XDgq/","R3GePZaiyrwGsRK+fbKKPrPYLz4EtLy7mnOnvqhmuj195Jw+Sy3aPTM9XT449AQ918qCPutCBz6NFi8/vRdCPcaz0z0Vx4C+veAlv1D6Kr5G1ms+HBtbP7MCYz6Lq44+RZj8OxAAKL4DxfC9uBIiPiJi/j4HEnK+J2OPPfhG2DzkdHW+tFmmvk25przFL2I/UyIWvlVMtrsIJ6Y+Cu1XPsrx+L27sqA8tJQcPhGWsT4ekWQ+xDimPpwipT2hB5M9uXGqPeEnhT2GPRc+mdGFPrKEfT6mKLk+TOtJvmOutD2hXcK+O7frveIQB7zO2yk+00bQPvsnB76dVVq+PwxvvW2jJ77WZIg+FYVdPh+wZb346su9ZD29vaTXi76pAdc+mY5pv+lPKz7Gwz89NegTO3+ykjnEItU+mGchPW+Rvb5iTuU9EyKGPZlmtT7Anhm/HWWmPOGMDz4YY46+kqvLvMGynz6n4K++ZLlrPr7AnD0tgck9dOQrPke8pL19Mro8McoavvVF4T5s86S+MQqlPk4inD4I8rQ9QDSIvgz7677eb6c8zgUqPkyqh77rS7Q9HCR0PsISkL3/i8Q+rNgGvqHmer63skY9d1ouPjKnxz4itsG9VL4CvgctqL3ayw296G0FvDt4c7xUcMK+GAoqv/PAc724Ixg+O9hrvo3zKj6QiQu8Sj1KvuoejD42DFi+yHu5Pv8U2b6gjkw9920kvsxaGj+JrR48iS/MuFL1zT0zMM+9I0hvPmXgIz7jwG0+GGYQP3bfgD0Noyc+CHaCPbvORTvgUtu8r2uLvpfKlT3rNlW8zL1evXo4wjvK8Z++NlynvjaIKT698Y2+d8RAPoJQtT1XGIc+KmU+PpcxiD4dDya+RHAevZlAAj9dNhE/w7BnvU2Sf73dIfs8Dd+aPb4fVj2BZrg7Yzt6vljzr76ROyK+syAlPsXtNj6fnsM+0W0dvXoKPD5p3Hg9hA69vrvPxz0ZksQ9k+mmvXW4uL3mFw8+v8vWva7UnT5d6Cu9","ldO+vuFMmD5Elpk9hdSSvt1hEz/8o9i99M4sP9fUnD6vN4I+cuOnvREvz70cGR6/Jhm3vpuLNz9Fvuw+n6dJvqK4D79tlP491IrxPtWGiL76nu6+gOAcP3SFOz8UVnU9M83NvjeFVT+AzfY+ol8NP0Gi1b7W/8S+8kaqvTZTlr5eDWU/Sh1vvgQkxL2Alw0/8dwOv6ZkRr50scK7/VUPPuzrLjyOvGc/GzbtPojYsb4F/hE/SItpvhsYI7/fetK+ggKKPis/ur3Il2K9AElKv5lrqT78aoA+zHnTPiBSIL3TYae9V6W7vgNnHj4uu0A+wLacPrubRr7d9mm+gpAhvit4Dz6ZwRU+l3ULvdCuBT7Z4W09Yuu9vmYxuT0BVdg+YTkXvpwBoTwC0yQ+hs/fPeHbkL5C9xe/p+ILvQ9cfT4F+Gc/YEnHPXT0BLxuJeE9UhKTPq1Y8zxIdFY9FP1hP/rv1D1hvhG8aWsRPpbtcL5UxZQ+6F88PlsKKr/UYco9LVsePXi+rz0WNFk8rCi0verpwjv/p2w+YxSIvFuFkDzaPJI9XrYfvvitej5BY3Y+9P5cPrq/Vj5E+ei+k4roPibzQ72JlvM9PtylvSnRtz6JORE+QTADPW01HT5AJ5c+UUyovrUY9L3rHAW9UZIrPucknD/Q9rc+7SU7PZKeRL7avWa9GCW4vgrtND6fQ/29H5J9venpDb6dom29EtMavkRQlr62XKc+v1QJPkTFjT38g+E9jLtMPnijwbyd38G+NQIHv8I1K74N2aU91LhdPVPOLb731+E+hmAJvhYKFT4yw4Q+AJlvPtZcmz1kafw99W0JPtAbCzwB9BM+BZgJvvPwfz2bIQg+HmV6vve9YDwBqMw+R6VSPhyv9L1P7Kg+BFhDvq5OZrxVG16+fTvCvgd3hr2+XFU8NqSXPYQkkL45WYm+PhNdvVew5D0UXQe+FWnbvXZHJb5Kmy++Zgs6vm/Roz5e6Vw9LBZkvfqtUz40wNy9WwAbPjJu/D1ajEs8","vjHSPLjvTjz22Kk9cKOwvGQB8j5vERo9JW+jPSP4T74eVjc+g2cMvtAyBr28Rp6+eugCvrzGTj75h5u9wXcXPjo+Cr45HVk/Qzy/POlb9L13miG8WYMTPotpzzyDiK89IR2wPaFqfz1lA9894kTbvqHqED74LuQ9yGwUv1n0qD22Yjm+5cWFvVSzT75MRQM9FtsOvXj6BL2RwwQ8MBK6vtN2/j1y5pI+v+WDvs4Zhr6ya+y84gPZPkPVIz6fHQo+Em7PPZjrWb7VKIC+EBL+vTxBMb6aQQK+QGQFPQucQ72eJzs+Cq29vb/LPL7CE4w9ExPFvSByVLvovLe+L/jVO367cL4zfVs9t371vbmmQT1ldjo/S0hYPiV0mTvZkQ8+8tK4Px9hiD/GJZY/HrkGviQzmT7L4lW/6ZnKvmfdez0fiOo+pFRNva5HiD+/cU8+15VVPejVKD4Qg4k/DZc/vl+SW7+u9PY+CSywPlaMKLyZbx8/AeJOvHfekT1jLyq+t9gov7eAUL51NHs/cmOmvmhF27taeJK95kPFPlfL376g4MK8VZ2iPrPt8r2iytW+ncgmv1Gkbr5Q/iu5fzP6PnCLlTwRVak8PjCePlSCCT+CVEO+0T+BPtF+Ij+w9p+9naTvvpWQPb/h9ya/ay4QPpLpkj56+JE+2vU9PxNSWb/P7E6+XzKNvjVWgz2yVl0+pns+PPUJAb9mbQs+NS7UPEcpgLxAqi+9ZvpDva5PGLz2r4C+w+UKvk0un77v7wQ+J80tveI2AD16yxo9bUiNPvRgbL7YBdC+egpYvaGD37uLAoY9BcQ9vQmchb2z2Zk9so8GvQ0v3T2lmRK+kiXcO11yvbwsfJA9Tv7QPhf/V7/T4zs9Uw/5vrh83LyWti49LwmRva39H76tIGW9cjNPvPY0Ur69gZu9aVi7Pe7IYL56lR29SyjSPOLQfDyoJWM+c86ZvYLjQTxbnmE+IovVPdQH8z2XZyU+oy4HPkcisT5OrY6+zDAVPqFADr5K2AY+","lRHQvr5NWT08vCC+mpcsvgBkgDwJZqk9lQ/Zvdlsi76xaYQ9LWOxvXw2NL5u0NC8z/g6vc4IPz0hy2o+0u6vvVzjFz5fpyu+E/4lPXm5AL4nLkK+OWH1voPOmD7H4h++3ebovfIDCb//IyM/fh7ivnixszyhrWe+bBw9PE2sWD6LfVo+h/yYvoRxxD2DO28+Fg1avfP0Er564dU+6+8+vX98q722byq+qKlrPUzhAb5q4E0+blQGP/2rl759BC0+67iwu160/L0X2je+IGLQPboIk76vFaW+wNItPSbVbr7118w6SiWlvuGNTD5aMLe9bNiAPgQ1E75ioiI+VW5mPbqcPz6HQ9C9EeiJvgaRYD0v7fy9FKllPs0ysb1HIkE+VNXtPVyOtL06aAW+QAcbPUK1Fr68BV+9iNAOPgsyvD2Xree9aFUpv5s0Zb2VfQo+GfkePpdMN71VZbq+eMvCPpjbKL7zxyi9XSAOvzsfDz4NXke9mPgUvtJMFD3xCLg9wzH8vfWW/r5ojKQ8p6t1PtGviT7Pj7O8lW3mvAU20D2ay/E9GqzqPb1qfb2lL4m90QBAPfUDgz4w5jk98nu+PSOyLT5wA829obYOvrQH/b1iJFq+grmavvx80bzmFLg9pEOUu/wtHj4iW9y8l++uvnmpG77cdn09LZjKPRTeS70MbLY+IgWlPfHPnb6EwPq+f+mcPWF/eL9WFnM/+v5CPW+igb+dw2e/LGWxv8oxnz7Tnr0+vYmPPRZ0bD8i4TA+dotjPwMEnz3s7je+xeVPP0NcwD4/Fsi+3KaOO6tftr1EJ6W+SDQkvvCYcL4MrJQ+XBzMPmgdzTzAcPm8XLCLvoqE974Yvu2+L3invXvhaz4AVlM+NT2WPZo7eb7xcbk8WhWqPRjdGD7HNqE+anskPo+Ezr6Dng0+aGvzPBePo7/Sm5Q+dRWSPqcWLD0TFoK/fm4hv4mA9jy2qq69AmZoO9muJD7K1a4+Agn5Pj5+Q780BDi+0LrevmgNIb+/RAu/","32p1Pm3iqjwLyg4+IIPcvan59D30QBW+JiODPbYHL77u5ma+m1VYPsIvtL0t1sg9PGgDvlRQdb2DkaA9cYGwPTykr7ym3SQ+ubwau4DbZj7qmAu9Kg7OvYYcgz7m9Q0+DiOHPfHlmr0PfbQ8DGaavqy3RT5WfT++GYw8PVm4Cb4cZTo+fmSGvR7zNL4hau2+E1PlPKGBIL1ZORg9RoEyv/p7oz3T2Wq9zvjOvR3/DzohkTs+nuMGPvRgh70M85g9YbG6PkQ8lr0BRym9C5rpPWpoojzji86+MhkKvqNlrz3MhoC9Fd8evpNHOT59WJK9s3F8PvrTir00uPe9sGcpvZMYuL6iOWw+IpXoPQpfgr7s8t++b2wbvduo9D1YkFG9rUbDPcWFqb430tm+MPhzvqAi2T3gvAM9bjpQPglESj348wm+KTOAvkH6PDwGu7O+Bt4YvxyldT5DzWU9JCsWPkSaD77LHqK+YkMfPjbABD82E2S+FXycvvLgtT3OJ7c9i/CYvnOklL6A8+y9QUDDvoy65z0A7qi9fVEEPt6jDL9bWgS+9A5gvs860TxIhtW97QKTvtpOIr0oB/8+V2JQPmA98r60fYu8u75Dvoc/ij1T/SK83iXVvXGWxT6qotM+HEnDPd9jVL6mbwy+E0cBvhEpTb2jCIy9iR11PvIAPbzaD488/CAmPam7R7/qVBm+CRECv10vlz0Qiju+usluPVfoZb4AoOK9xfv6POl0wT0xrYM+G3L/PWQ4/T1W5AY8BwkiPautzb3hoes+9SFCvgy14D4jSIi97zQ7vilWiL1Ylpq869iXvfGE6b4V64m+lqp6PlAEpb42xrU9WaSkOwN0I76vrwe8mYV7Pbecmz40EMe9zGdivUCeAr7vqYG9472CvR3QYT61Fck9/I4oP8zMJr4dIb08G91EvluHgb2Z+1k8SOwLPne07L0GM/08+7Jevkd8ej6zRd496GBOvqKob71MdqU9nBq2PUl5RDyb3IE9TeGvPBXw+D3hcMk9","1L14Pjvw/rw8M2I+zykhPXlmL79bbgu/JvAGPxzaZz2QlwU/upNAvUQ3vL7+e4c9DGARPtUS8r41Kcg8udXXvMgdfbwMYAI+80rwvbZ60rt1hru8nS3LvG9hST41JAs+izgMvpnfrb3W7I0+rpV8vCzjYLwk3wu8RUQKvmxWgb6DyRG/X9rOvbEShDxYvAg+Ifd0vnpEEr4dIbK+EVSivZkkUz2l2MA9eArDPYAW1jxwxYU+OOx8PuEgoT0UqTm/GvEVvnEbZr4vgAE+qRSovBMdCb4LtYe9jPKbPqcFuT5EkBS+A1GgvQEPm74ZCiG/etilPazGKr6PPcq+R+UYPrpgPz5fTIs+wjVbPibIqD59+809fHjgvcwQiz5uaaO+IYUaPgjfgj3xcy0+HFoavoANN7+WvLE+6jLfPhe8Rj9gQLU+9ps5vkw6gjzN01Y+UdL3PUpyfT3eDbM+cOyAPqqwjT5GlVg+qu/OPYqcRj59uBK+YUx8vf2DuL5QI7Q9YtQaPInA0j4Jb2M/RdYDP5qyOT1QEcQ9TLLvPiIC0L6/fyw+gVdnPseXkT2+QsE+0stPPnl9jD7m0F4/mOjmPmlliL2vwM4+ySVJu5tdPj79ry0+aJhRP0e9KTtgpF4+VmnEvddQ/j3Hc+A9PNQVPq61CT+4qlU+Qj6nveZvdr2NVae9CWxPvq6mC77KUwy+4sGGvg4agD4654a921k0vbYMLL65IDG9A7cQPh+0rD6QkMA+cl18PlpVzT0eZKO+ULQZPpU/yz6iyRE96ri2PYdEUL4Ska6+cP2jvZuscD70acy9GBttvhBFazwcaUQ+D6NsPvrku70755Y+T8mpPVvOgj0dBm69gbPYvtl5ZD6hBx2/o0nTPXzOkr6Qfq69W/a5vbDWRz0K1tG9Vbl5vgAdKz5tbgC+5qknP03SiD53XIS+5dGMPfx/CL6ygQg8Aq7GPhJg9rsb8os+2rDxPkXYWryvDhe9pJMwvkwk3rsGm6++3AbAvSuBg75JmDc7","TsKHvRHM2D1aQNO8hxuIvr4tr7yOMpc8ADBGvbjd+7p923o9UX4wPv0JRD3lMgM92zhOPt09/z5TpCu97+hTPhy8QT4MOCQ95orKu8eCi75RchQ+iMw6Ph4eF7v6LhO/bq5+vbBKoL2p0xC9aUywvnkZCr9OVzE+tccOv4VBzj2U2jq+/zfEPek6wjxu3di+hcgYPs8NEj5fri0+osb+PUQj0j1ORVM+WpczPgm3qD7sRkS9qvg4vo1Alz71G5i+rg14PhKvij7W2wq+JJtSPySIBb3iufy9O1WGvepERb7KqGs90DG0PiepAz2oL1u82gMmPipyjj0ogTk+Gwo2vLaIO775GhA/wzl2vkBL8z5RIC2/IzJgPdDcVj42yl0+VEwtvgmlCD+WgNS+/bsXvqFE4b0S3KG+Rr7yPT5qBj9t52e/a1+kPp7+zb3O62O+ybjBPwEzjDzT/EO+9PTZvm4/WT9vxiY+zzYpvyiKvj73Xdm9qh/Fuw9O6T7YL8i+nNh4Pi+1iT4D0pM7IHi4vZKBuL5zqjM+C8pKP7Mnej4k4NU96zcJP+VaTD/fPZm9Ci9fPi3/tT5E1IG9hxdnPX0zj7y5+Pu9W47KPXoye74hdQ2+j4NBPnA/Wb36smq9I4nFPnWXuj6MBeS9D1OXvlHDcT6firy87LUhv1gZjj8MvGg+LZmyvlPKRb9uTR09C9EcPUqvtD3oY9S9+P2evsbB2LysODC9q6BRPSym8L3P1JU+9NZCvOOGkzwhMUm94qNdvixhzjsXfKI9raqEPhulSL4c4kK/jsD6PYG94rymKp++iO1/PX+sDDxLPyO+L16Nvm/R+762zos8c/QsPmD+LL78feC9rXqJviwfmD96vH++qeK9vajJOL554ga/ZUzTvcVr9jncdJQ8xnQBvnUUND6/uOM8gZF2PDlnFr5xrq09J4pPvuNyhD1rYoS+/lbUPYe0BL7j0NQ9yuS9PS1vi772n0a+woGEu4ApNb0d+8K8Q8ifvuUcgL7Lz1g9","GVQov1JWUj1ERnU/oi8NPveF+z2S5wC9OqYwvuHmhj/GLpk9jmSRPvFzyb4Ff12+QfyqvaHPij1EH+W+oeyzPSyDpj0Igny9DFapvo0oPr9e+J++sCoOP2/BDr9yxTW+H3ncPrSdir7zS5a9zsLZPjeL070GKIM+j1onvZf5A74/0/08tGUgP6NDiT2QhHu9gg8hPwsc3D34X8A++ykFv2vm+D3Wpzm+Wrc8PY/oLb5N/Qe/8069vtLO5b6feto9sbohv5J16L0+C9S+k4uRPVdi+r6tJAc+e4/IvXoMEL/HK289djinPbf3Mj7ZeNi9xU0tPtnfuz5M/PA6bCBMvqhq8z0nfSG+i8ogP79sSb7OzhA/GH5PPZR34r3oa6i+1I6nvhDgPT2UroW+dX+IPg5giDtTsbw8r8AJP9gOC7+FBME9POoOu8WygTyp/be9e8kYP3jPk74YYAi/B7jVPr+kEj83/zq+tjUZv+bkEL6fuRs9WqQePzkx0j1IdM09+jc4Pizoyj5IH1o+oHmwPttlDL/J2zs8g5G1vjbKGD5RkM89eW6fvsOnhb5n48u96psyvtJxbT6zgx++nVXhPEu0fT6nggO+1R5Uvvi4B738N76+PQ80vsfFQD31xk4+8twVvrr1uD310NY9SEqpPWXJYL4wrd++QhsdPYQmqzzye3i9iUMNv1SZK75pm0i8NNm5PqJepj4c7V0+UMTCPvTBub56pQe/XZ8oPc6Tx74LrZ8+obshP/O/hj+aoHK+wiiUv4Ks4r2vbWc/6e8OPnHOgb88u7a+Aap3PiskPr0B8QG/uZpXPqgAgj6yVfe9ubpwv+Sc974ti5e+wtNMvgSSMj9E96i+WFqOvhuLEj8U6xu/WrnqvbBKwL2y/Ma+ppXjPYAuqj3N0sA99kcRP0jxMT2Jz3o5++YtvgM9db+YeFe+yRB0vhZlrb6CEB+/RnUsP6IvR769wFw+urRwPUu8+L48bBa+ZkyaPscK6T55u4q7NZOhvRXVjb/4EQe/","VkpmvQJLlT57NN29xKQvPl6gFT1xqZa+vsSoPbdxX77Pk5g+4T1Tvb9lwT4jux+/8Qk4v2spLzug0wg/MbwGPso8/z2G9Ue/EbB7vgY3JL6cVhY+EdWcvtpyVj4Nz+M8DeR9PgaXjj4TLOK64GU/PX+AlD76vWO+XxWOvofV275KouA9U9uMPrnJMj5XxfW+tfAHPq+nEz0uxkg+6hAqv9WqNz6mhsI906yQvqY/Wr1Ztcq9De6RPPMZHD9a534+O53RvKRZlD3rlFO9VYZvPcO/ID7PHeA+ojpLvvMpqT2urNC+pjUMvqKJrL08xWU+Y6XhPr3MGTwoxQK+A4QIvnj3yD3tGio8LbCaPc1DMb4AjJa+zLhXvlgOZD25n5w+a8B9vpPBfr0shTG+POmwPsHJDD4WsNy+Q8+9vA4FCrxxjps+9NgNP7gN6D3y/SY+Y515PTxeQjvyWb6+MVn5PjhPyr1ZPyQ+2GWHPSVFKD9kvho+yOnpvMT4Er4P5qQ8D5sZPZhqpz5HMgu+wSD6vqlKQL458DE9/aX4vteeGb9khJ47SRbbvJY9kDxAjLI+8TyGvgvRRD4+bc49Yja9vY5jhL5LJEQ+/jb1Pd+kQD5K8I0+PNKNPfmcE75Raz08FwtdPvjI1L7JJmq+A+QbvoleGr4e5ik8BonRvangBr491yw+xHMkvfZ0jLzaWlG+TuYGvgkoVL2BTY8+24+Fvr0n6T6EQHQ9ifuIPQ+yzb3dXng/hKx+Ppp09b0Afvo95LczP7nVej7tZxY+zBpXvglZRr6DXpu920WDvt10DL8HY4w9BhHEvBSqLj+j2qy+1nTDPRFJdD4d/qa+c0dsvKMt3Dmqbfc9vSu8Pqz7Ez/rPpe9HLidvrzmoD6PRxW+4B3zPRlFgT7Bed+9S2BhvaVbtb0vURq/pW+1vjr7Vb4x7+w+PC7MPa4AST5nD8I8QNSsPYt6P79WFs89C+vYvdc/Fj7JxUW+B8tKvixdEL6TQLS+U8PWvWfLErz4wlG9","H86ivkvxLL7Jylu/WQJXviTZ5b6qrYO+NPyZPZOjqjtudZc9KKrNPTCOl7/MUKy+NE0BPQbdiL3wLTw9WUXEPVA+sz4vYtq+Pj/eu22PKL+k1O0+NAlrvuNkeb9AO2S8IOVaPWGk2D4Ww5S+kz4ZPYMJUL9cgAU/3LgHPzwxZb5/emg/F0K8vWlqYb6YvsW9fUSZvtN/Oj2m1Jc+SBarPjo/PL6iaTQ/c1CdPp8ny749FuS9zka/PV9ghb1rq989KGcePaYVO74B5AI8ZCNAvpBbGLznAR2+UvuiPnci3j00b789Y4jQvrMLWb90bxm+yfBfPmOkmb6C59y+rnQPP1WpVjytw8Q+MzGUvSt4sr1Wx9o9A8JuPsVmX7wtlo++TdjWPW5l7D38Xn48O5Gzvfw7ET5kxLc9QJfHvob0HTyBwJU97wCZPTZO4L0Itgg9i1eLPbkWFD+UhC281N8Xvh3hvL1FvA6+aeRXPSmWAz2xaQM++rTuPdBgKj67oC6+wZYcvQYDEb160Uu9GJN5PtYkBz7GoJk+fiS9PF4HET+R8cI8FtvkPb+gNz4ALtS8Z/CpvXncBr6v9DK+Rq/hPTOS9D1v+zU+M8qgvZOwYb6ponE8XSL9PrpjI7xBHoS9k1+KPRuZWj0StcQ9RPIMvnJUsT23jxy9O5klPhmHwz1J8Km9nS8SvhVxWz07AgE+pJS/vlVvor0bOz0+qGJBvS/I4L0p1Ly9BWKJuzXZQz65/BQ9t3nmvaZP8D4U6Qi+J4DOPJOqIrmKaXk9QObyvTnd4b1yYSK+HFcOPvoNz7zynpE8FGbEPWjqFL2m5c8+UPNcvnrxAr+LO0I+bPctPqTghr19V648aJJCvS3EQD9MAXq9WHGLPeyU5r5QegA/Ejr7Pon8Mb6nfw8+VS5mPl2hJj9hNpo/xLMVPgl4gL0nxaA+xYamvnYlSz664wm+OnAUP9mkRz8A8N69t2kFP1FxgD6s9Dw9eKp5vDgUPz8kKPu9jy1BvcVHgL2eIiE+","Ks+FPrcSNz7Svyw/UVnwvFGa6bsA7tG807y5Ptvu072Jwpe9fcjqvOhsUj7SUsK9YtLHPROP0j0ncS++u+jlvUM+7jqcuEa+CG6xPjOIzjyuawy+dK3qPaKodj4Og3++0ymCPAMdwj47tLS+tWYQvUSuPT7V6/u95FvavKGKAL6xI449HtyDvO7gmb58p0K+2O1VvadrIj3T3Fy9SUtmvm5gmr53B8C915pgvVzOhb4Hmu4+Yy4PPjbAqz43RTa9J8tivphcRL0/Jhu9B1jZvQctkz6Zpzw+UaLivNMypb2TBja/P4qVuuL2S774XWy/DQ1MPl90MT2vrqA9pxArvreBqD6Mdmg9YsXGPWqyZT9roa6+HEZHPhTuw77Tyq494ZMOPwcCIz/IYQ8/6Ji+PQIfOb7kXOW+jmdov3JKnL1o1YE/qtEJvjptS74ZoT+/zLIOP1cmhz5C38k7WD9VvpvBgb+xYYg+VPOvvrun7j3K1u0+vN7OvjB5Ej8GZsa+VbpvPtSxJT+490I+j+xJvdMdob4D0j2+bUu0vhAxtzxX4Zw9qmgVvhCF6L1yh6e89NG/vlrgKL5ebkG9UoYpv31q6L2O5Yw+YJ0EviGweT+bvoM+gkwGPsao5z2DkJs+tR3YvKFGJb9SPuU9heAsPxDEgT4yPBg+tQV3P4BiuD69Rz4+iUYNv6gFET64IB89TkjmPRqXczwG5RM+MfGUPrp1yD1NCKa9jqhBvcDfoj4kYY693IE+vK2Stj4hLzG8SmdqunhvLjsFgOA9NdOOPeF+Y76umM68Rj6MvK/7yj0pvZk9MaP/PH+SDb1q/IC+YSKMvT2a0Ty8nAm+0iE4uNotdbv8RfE8ZvS2PXfOAD84BwO9DZ7yPL7nz71djjS+YAw1PrLtzT78TmE8FxDGPX34eD4zh3U++oGSvBslX72CDR4+w429PejvRT7CtGE98BC6PYjz+LyX1Yk9AmYZPnVwnT1Zq2g+V5b6vf5jjDwifyc7V0L4vFk/Lr47AtW7","sMOfvsYHWrrmRwi+TwrZPTA0Mr7GmiQ9LvXUvoIKQj7ROSu+0VzuPTUnhj7SLK++mjA0vjrAgD0y6ni+cj8yPEXw273L2gE921QFPdCfVL7qZvc95+1VPpawF7/pMKW9bLICvkUbaL4S0fg9rpcLPjNuKz7Mz/O8CTGTvZ0urr1zwkI9LFDuvJRzLb7s1ni+wH0Ov3DEFz5rEuI9o9ZiPj02lT3KMAu/hfn4vbuHmL4Nm2a+sFFNv4FVrb7jaLO8yLLYvlPjjLyQf7i9P5XZPeP3pL7fSpU+NZGnPXp6Db+nd3c9mrgSv2YPkT2i26G+2LJgvakRN748h2c+XaNovqF1sj2ByZ29JE/BPUuimb2zABS9cB6SvfgbLb2XyIQ8NtCXveKj/7zxkD6+Ayc1Pv7C1L6K5pM9LCFKP7+F2L71ywO++U4GPsollD0KiAg+23bsPZpB2z0qWsq9bTBOvo7y873v12u+5BP7PW1VVL0EzBM9JIZ5vjxhBL4rthy+Es3tvaBtgjy3fR49kTFsPYXQgT67nh2+UyndvTs06r08fgo+oBxHPam2tjzKhDs+G8wivo57IT64Tgw+ovHiPFCpvz6Vcgs+E3Fjvvipabx59Zq+gJyRPUakm7tBYc49z2gYPuj3wD4vAem9gOYCP0bfB77qNV8+YDimveK/gbwF9Zk61/oZvnt7BD9WM9w9umOQvjgEcj6jtDs9gQDPPXP85T1u5z68OZIePkc9wL45dXo+llA1PpV3aj4XJJK++70qv+bbz703yxQ/it1mPS0JwzzuDzM+a4KMvpAfBb7XSuM9gpf6PXNgaT5sVVK+2ruqv+aZlz0Lywi/Fm9iPfq0jj5yAK+9/OoPPsAaTT75V7W+Ho1+vlaqFz5fIHK+rKeWvc0y9Ly8+oe87GVuPakUgj4jP8u8J0k0vbQj375Eyrq95i9wvq5xSj6YK2+/3tUnvXFYNb51Y4w+n8QYv/kJALzLSec+mUkcvqNeAb1smRM+WcX5PcQcez2umyk+","1ls3PWp1ND7wOVo+isM2vt5pyb5VCAi+Ih+xvsp2p77y0QK/aYEQv9Vejr44uSS+xg9mvqYl177fCR8/up2gvgNIAj6KKBW/gF5kvhhuUj71Za6+JiBQvnA9Gb7WBTa/SXhNPmVIez4dPAW/bKrvvj7em761T0I+E8q6vpRd6b2HmL2+fXE/PrDloD1yOxc/GboePtllzL78ukK/UAFBPjqNhL58a5q9CgnIvtGafL3YVQa+pAoYvosHpr5vegY9XyCVvQnLnD7+OiQ+Zo1UvUV91zxzDwU+f0hhPqNjJr6I2zg+X2pdvmbK4rvCzWU+czHFvq4AcT61vj8+T9qGvWMJsz6aRem+/tlHvy0Jrb6F9rk+ELsCv0A65j2NorI8nsoTvwggjz7lcTk+wBY5PrYvYjw4LCe+iBmMPolJ0T5VsdO+4WADP/q7kzzJG1Q+ONmhPoYcT77v2PA9YU9uOew0Gb+mGVA+8RhSP+Elfb1U8T8/3FlUvRy1qj4y9oO7qe1DP5THXr6Zyfm9WUOnPr0BzL4rVY4+U/oqP1SW974U9JW9IJpiPjGCgD17He68/up9Phfi1Ty4MpW8lTuVvpXlQD4+ZX6+fKmjPrjXFj866qI+l7cDv/0RTz0CoHc+wZ8vvkxpMr0y+KG98pQsvwzCn77ucHq/JRvKvp7Xvr29A6G+ou4JPhJfEj3UFls+Jw68vIdcAL9egJ49xRZjOxhvpz03AvA9BN58PjmqH75mBTS9x7uBvuZJk7yg9Rq+TgCMvkG8Ij4ba2Q8thMVvlu7P77tS6s831wTPVyQBr/A322+bbaoPIq5/L1raIO91vz1voZPjr6Ps8G+ibcOPjbrAL6+TY+76lJEvifBPr0XSZI9ekL7PWA4Z758xOq9ZawFvfRxNT5IIim9bJbDvW8VOj4eTUO/jyjXvu4L+T7d6Zy9LNz/vD1BSrwx+7m+iPVSPsDoKz6QIk2+nf3BvEBXFT4IqrK8lucbPXiCbb3vqCg9r3AMPsTLJr7W6wW+","bt2aPZYkOb207SG/KdYYv9Fqrb4TsI8+c/vhPkdOKr470QK+Pi8WP2LVrb+sNgu+0mJLP+orIb9Ce9E+sXYtPq5TsD49kRC/8LFRPmOg5r0WHH8+1G6dvss2T77woVC+JWLvPm0yl77wz3i+gcgJv15ekb4esb8+VkM5vZiZtj2xjfc+610Zv9Ly3L6LZFk/O8ANP6AwsD5Fwmo+tfW2vvJwr74Na7k98BHqvioZlT565hs+BM8KPug8E71juu2+0yB8vwAslT42GX6+PMTovuU6xr7j1AW9wbjAPcJTtb5eZ5I96042PyEDQj7Dly0+RhuDP/qWPb+4Mxy9RNUYPzU1DT29yIE+1SehvhGooj5GNdY9DwEPPSYiZL6M4uU+Fp/cvBGMPT6iqcg9JZryvbH4tbztlAu+cE2ePqlGSD02taq8bcc+Pn0atDztTIs+HaEDPixx6z4SCAQ+hseWvB4LT74wLLe7TdoIPV8ShT4GUEw+eVURvgRO6j3/qqY9U+1KPiggDT01Hno+/TIGvmujXL6WHrQ9HiNLvoKtRT7Zhus914vBvTpgCr4VeZG90jjuvrXZeb6Qo9c71xh6PWvwcT3I/Ia8YGUZvvZXqD4fNSK+CeTBPmoXLr18J3u+jKd7Pie0nb2ZJUM9ug+gPDs6EL7xWGs9nwJbPwnzqD2khU86hJI9vaJMMT76vzK+YWC2vlgtXbw9VJY+m7icPviCtr5fB1+9fVFOvsDWsz5zHrO9zugjPdRXYT/NWWO+6u67PREM2b0duKW9W5aUvu9Ubb2lUjS+3Fq0vbyWoT2uEo6+C3aWPqgaM77/zBC+r4sKvl1VADxBQgC9N3ojPlcXH76lnnA+4Xx0PdNCMD/Rgw09dbSbvfFznD7V6KS++7dAPYYFsLzmEt89nA1GPgUF0j4YAAM/AbeNPuAJTz7KB2y+dcVYP12POT/Q7pi+y7kAP2YR3z2aNsE9NDalPj8CCL5sCHQ+2eafO8pG+j5A3Gm8cBtGvlpQSD1VHG4+","D5JIPt1Fmr5BrBU/T89gvVLzXD5bZIC95VsqPmjPeb6MZgi+lZMcPXBQDD6qhYm9GA/HPkURA72bFAu/WhogPvCiMb4kobS8cYYDPI3sv7sCqfY9YUjovpG7YL5c9+e9DElhPRIIGj84wKa+5VWVvkhU8Tz1NaO9IQvyvGZx+T3iaOG9RrF7PtssH77cdZy+auQLvjIrzz3n4g0+UB65PmY7oDyvZ4E9NiBxPY50Zr0Ik6A+vQAbv/GqHD5klCe+TskOvJQCrD3fDMg+m55avYtTPD4xe+O50OyiOwO+hL435v+8tHRevT1XaT1V7ge/DLmSPUd7hr3pl4o+b7s2vzvlcj5jKEo+waO1vrl9sD3pxk2/5TYGvp4R9zz/QI+9lv6XvSUUkT7Rh0y+YaT0Plu6S75wHeE+kJ/gPZgFhj7WTEU/AoVSPt3zLz6Jq2c+Df8nP8RXmb0iw+a+qka1vtkET7+E7gK+1kzdPk1KCz8rRGM/37JYvqdor77uK8W+Okg4ParEhL4X/bw9/fzgvf/o5L4u3yY+AL0YP8PgvD61Hgo9xwMEvjf0WT5U8ay+iWWBP+S+zL0bZLk6a3Uhv/mkgz6+Ko48lgwqP03oTz6gLWu+x601POhsc74KXxK/F6tIPflxMr8dAnk+DiTpPdhVAb/qSfy9iHHtvlMq/T7ieN695u57vgl7FD4CVTC/IyDvveyzmT18Fzm+Y8R1vm6Ubr4QiMW6kgsEPnHAHTwx4Ri+pGwpPt6paL+X9lo7M1eRvmLpzryjAWG8jplrvroQSL4Ncpq88GkyPNlldzwBMxq+uMyKvg9Bbj6M/za+apfCPdWZ1b1CYUM+A/E/u50Ldb2JXi6+FKxzvlFFzT0ptJQ93FpAv1dHWj7VES8/+ku7vC459j2P+qG8kpc6vjIof764DTs9odnWvV+onb11nv69cgRrvnntP75CNri+IEFMPVi1fL+udTa+fTCCPZTyFj6TbYk927ZDPnxpBT6LydC98FuSvu4cPz4Aou29","Ue7MPZbUGL+bx+G+D76pvjsKq71UOQi9SszRvVQUlr5lypA8MXnbPUfJhj3S+cg+3bz+PfUUkL1awYO+3Ob3Pfx7/DqFBle9fKEuPTGBbT2w2ck9D83VvtjaNL8epqw9zXcrP708eL5RdKC8wytjPorhNbyHL2q+aoCtvaLr2b28jvC9xENkvg7pdj2LXm0+2r8nvuyLh75SvHo9ZI56PnSzTL5gtQC+WyuNvUpw0733niG+PFy5Pfio4rwzVKK8DBY6vbUCVj4Ja76+IIVYPr2DHj6f9LS+kZ9nPXsHhT4rQrQ+H27cPiVN9bzJY8K+ZjGAPRw/Yj/U1SY+j4PuPa+uMb5KOPI9mStRv1yvzb5SL7q+an6KvoiuIr5pGFw+mc6RvMxYJD6xBCu9UAfBvR2vpjzyWw09wSpcvnlHDj4206283x8SvxbaFL6UcHA9AVuFvCzFxj52EYC+QhfvvUZXdTtv+Zm6YdTbucLaPTyCfPA9fVd9Pp5VZ72nPHI+eZ5wPgugIL53BkU+H05wvuF/xj1rdYE+qpx3Pr8/Yz2i2AS+r0krvs2Srb38iho+AtUdvenOCD/WU/O9ZLq8vWVqFT1cn788nWS+vCOn3Tw2a5M+cxiMPlAOhL3+Bku+bJ4ivj7s/7u6NIi9llqoPnMBGD7SwyO+2dXnvaJeHj/amAe+ZxNKvjsyYT7rtp+++Q1FPz1j5z2ARB0+4OSCt94YYL6DrEy/ayYAvyTAQL84Azg+LlbAvu69WrzgxAQ9fBqsvneyGD3n3Uq/VR2WvSvxRL+oI32+wDy6PgKkQj4/Ovg9cL7EPdkBhL+kA8g94Fr8vm6RUz01NnY/MbUaPZnVDb+ca9i9pfIGvt5hFT4FIyM+HFeGPXag1L4aCZk9nuSNvWECrbyTFJC9zX38vTqnW73Fo7c+7nGnPbo6mTxxEKE+LVicvk4SzjzYoDk/7iAPPuieQ75gvKg95GNMP8seH775fM68jgLOPhr3K78vkqg+HvY2vYLIuz6QiyO/","EjS4PJ0FeT7/VFU8rwVmvRlMqzzBvt286azkPTFHAb478Aw+/1wlvfBRxT4o8RE+SmScPkQpXz4hYYg+fWXePW4qxD2OoRq+bH86PQQYPD5dsQu+7VxJPsY24ztE7aC9WVZMPm4YNz6jcMw+0Xz8PaWekLwTjjS+nROjPdkunD4zFDG93Y/wPMzo973fS+M+n11BPgcrtj6HdhM8Z3P6PlpImr3lZ0c+zW4aPkN4dj4xfy4/Ese6PZaXbb7fluk9NPBCPsh+k706laY8R6D/vB6Pzz2jgrs9N/m2PMdoJ73n0Ce+pamXu6MI0b3pRNM+BkylPmMfbz6foMU9K6apvfDpob5e4b49N6tpPueXtD7n/KC9pJ9oPVIo5jzSD4o7JD6DPWnPlT7g6Ag+dTUUPXBfdL6J212+Q35WPrDUTj0LmhU/SaxSPjqsPj4yRze97/e2vXfuaz1Yqqe+8AyfvBIyuL1RN58+xgOJvsB/4TvNNU8+/0SzPpq0sr0wUcY9TtRuPqMJgjzbfaI+wWVPPAGJg76bygQ+Ng+tvaQy+D4jItk8/Ss/vlvRjz7Tia49gp0avkIOsz4E+oS+rKH+PSC5Rb4PU9Q+3yL0PetJWj29MRe+CeSzPnixRD4QjiU3H37rPfjlyz3wS8O9p0+SPVx65z09Eiy+4CoNvq7JXLzRpMc915LEvIPBJL+uqKO9O89DPud7Or5yxVU9ArTTvH9muD3IxQw+KxxQPJMdVLwy/Te+WGusPQCbqj3zLAQ+/4ScPlo27L4/wcy8+vNHvpQ/hT5MmKA+ZRwGvlvpk7waQ4y9achTPtUtyD4eIAO99uEXPlhJJD6nyL89OiwtvLzuGj7wyQC96CSkPvnPi70FWXE9k3nZvGSfUj4ylZ07IqkDvvRl9b1JAIU+UumqPHdO2DxLczW/ZS4xvtcuHL24BLI7q3OaPo2+JL68ITM+fvw9vVhEID6sda49jteXPl9uez0LjYy9Z4zZvanrzDr1iv68iFKyu5Kdy71T70C9","hm0ovtibg72HGvQ8MAEMvvS7j72g9ic//QQEv5qFCj4WILu+77gCP8bkUT+m5lS+ScZhPi9vLL/Eroa9WdndvRmSaD9ZNaA9xoNIvwWbdb6LeWm+VOq9PXOKU77eifo+PG12v8SPNz4jpg6/BnBNvj8XPT+Ep809ovULPx3Y9T7I+9o+YkWrPh/9mL3N+gm+rWB+PqmUlz3dfmG/Af/gPnpvf74eGps7D1AQvrpNtj33Ggm/Bpgrvj9S6T0aBwI/0cc1vyYoCzpiPIk+8DgvPt46Rj+Nva49YC10PmVQtj4I6UC9C366vh41M7/Noko/dkUOPugT/T6A1Cw/99JgvnNzir2tt8Q+y4CuPDtj0D1Q8BM+nLrAPnIwtL4gbSI+lp8lPuCzIr6HVa8+lyAWPkU8ID/GaLy8hKMUP0xJ3b5TgQ0+RAqnvlNPJL611KI8G6aZPgIH5D7J9E4+EYfBvQTZEr7WBI8+/BmPvpETuT7AHoU+FCRUvl7FfTztTuw+CNWCvX519j2f12++eU97P8n10r3IXRG+zO/1vivQED9sNBe+6/4bvRqYXr0YYZw+BHRLPh2SED0e6+q+9jHPPvywfDsSyzu91WkhPqFVtj3MFcg+/Ax0vsX9gj700yg8Z6CLvgt/37ytk1a810G5PtLGgT4b9iG+hh1YPrfZozxxm5I9EjUKvq9UNz/Kubw95b2cPoW4W76p2s47AAsZP1FZQ7oC/xo+gocDPrKUhz3KKxQ/SHbkvfDWtb0YmZG9F5GkPcISQD9Smi6++X7EPszpoL1pL/M+zp5cvvySlz2gmYE+LMf3vf5xAj7JYAs+nRy+PLKMHT5DCBm+ghgIPpm1DD/8RQe9xj++PJpbbz2hk7o+GzZbPgvzWD4VJXo+E9WqPbCJ1Ds1xFU+aG75PVHjwL0zYQC+7V61vfdL6b20Vxa+2riFPgigF76/hWa9a+2LvgGn8L0ORZg92brhvQcAtz7sSqe9IfMWvhehf76hKLM8WqrNPpQUWr5Igxk+","zjzlvSkLCr57TNU+HkrHvvnIeT9FH408rPqaPduj4b5BeJu8IwXhPRnSfD2Hm5A9odNXPBDGXb3CUDe9rhEIPsI0Ez5jjR0/3YdDPXMU2b5VL0Q+O9PLvmxXrb3Jk0g+lVrPPuBsEL2tdIq96rXkvk6S1z39DSo//hAtPq974T7bKE28aj21Po1oED3la4q9EZXavsXAYz5MQ5k+Q7QbPty+f737v6e+0dSlveeSe70+DYY73FgUveShnL5ZfEo86xnVvcgPuz2hpkY6oqXQPLiQ6zyeBSU9eS6/PdX+PL4M9r++3WI5vjZIJr0/0fW9vbA0vjxf2b7Bvj++QItYviqTn77i5C6+eZDjvcVc5b19R7M+NRKtPtZPyD6bT/C9F14dPj5/iD/CXsW9tS2OPoPxYz6cwpe/12aavYIFe715R5a+x79evP81GT8gN4S+dqXdPuY0VD6J8Hs80POGPg6uwr59fz8+9OWvPjTeGz4GMEY/MKSlvRDVYj/l1pW+KVezPm68zT0wHN89zYCcPhehOL7ANdc9X14nP0HcHz50g4U82qfhO5W3Zr2YrpU9ADBLPvm4Zr52KRu+JQJ8Pi3ckT7JL0W+kJlXPnuYHD61xS8+EK45PZYklj6eEx8+Hc7MvjmHuL4UAnW/22YNPyZI1T7OtYi+8heLPv/z7b3IIZA9hD2OuUbMgD4hzGw+LWYyPu094D1OB4I9bt99PmwXKL4TDSM+LQyuPRhawj2FLhc+r1wIPqMAk7/W1iI+tX7TvV4P0z4k51C+IvYrPqBqdb1MpD29gX5PPrSQqL0V8027SvaZvYxjJb7++I++ycIRPt9A1byUuUg+UBBIPlBnj7umTsw9Cr5luPAyIz4vdYs9up9HO/QKKr7vtdC+sUa6PtNlhD6FXog9zeyTPi4tYD7IUBu9bKopvdbZGbvC6DY98F54Phx/Or5ia3Y+d80tvaANpj3cziM92utEPkmGFz0DZbI+geUtvnWAsL0EMX0+FK8kvXrFQL2Z2T6+","E+CQPqOLED4069w9qwMzPraykr7zqs68T4npvZAmRD3fHmY+Jp45vy7UBL9kLBE+d4kQOyYLj74OzSq/uH9FOxsW377jEmm/7XmWvuh5izzth24+wGn7Prdt/D15prk+TMWCPmu4v76xwoA+mSsEPjSidb4V/a49xZ0AvvECor6yZX299wOWPg2eDr6PDVm+7WRJPlrjQT2yoc09+/BAP49IIz2hEBO9e/kYv2ml2L4TAU6/hKmrPp1RKL/UYCO9oLuyPY2hvL6/QK6+Xc0jvtFTlL4oYYw+clIGPmCYVb1g5em8qc5mvmEMLD5jRco+RoNmvoELwj4fXY49OS+vvt3cgD6eT/Q9/YNuv+XTDT0c584+jzdhvtgrQ76RbFo8NyI1vRRRS74qYnk9gVNWPivcOL7otBI+lUiCPjaO/T7rsJA8RgyrPpwHHz83U0q9utrHvSeCjD2fBwY/faPhPssE1T0ohre9Eu0Bv1NUyT6wRIk+Hf58Pm4Ouz5RT3e+Pj2MPoWHVD4AvZ+9CGL0PXK/Gj1Oylg9Zi/evQhczb2cXJY9Kv6uPWTakb4Lnn6+RsruvfgDOD/RAEY9LkeYPdYbjD5ORea+XFtmvjGoTD53Fem859Ipvor50r3m0bE+BFcnvsHFxj36WL28+vnwPZteR72j5tc95NXZvUQYwD2p90A/qQ+rvcgIH77d1ys952eYPlq7076UmS0/imbUvROk47s1lEu/QcCmPlyTUD47asI+ZE0BPhYma7+DnT+9x4UQPzEOwT0YwJG9R/AAPjOPsr6N/c09TWtxPwMoDD7mCwQ/S2Q2PkCau71jLgM+8HSCPxQyP7+Dn14+ZvXDPUUFp795Ejy8muiOvbIW3D4wHzM/BM+nvR1GNb83JuC+Y6UtPjnWvT28nqQ9zYpQvbk0GL5z17I+x82WPdVQvb1NYi49SlHePunNkj0QQRY/iQmXviJrVDyypAg/ub3xPt5d2L2bopM84vGjvQpkt77anEq9SUWTvk9Ymj9W+Fu/","9/+Fvs6t5j1zVSo+oNIvvPqIvD25Vqe94MgtPO+lwr6kIPs9XhvAvutwkb4PGSy+tva8vpwo6740SzA9qvp7vlkefj6hox+/ZZ0Uvm5EYr4cgiu//Om1PUG5Tb6B3ig9O35xPhuygb6pyBC/YvpaPn4EvL5ewQQ+sGVwvo9KJbxyaHi+iE/HPoZrib3zA7U90cFbPjh6wb5dGke/kxWoPhjlGr6OpSI+1pzzviPuDT5MWP88BrhYvcX0Nb4xMTo9/IBCvg8mSD6LnZQ9InyrPS6by730i/M9qcaVPVpJbr6GlTY+4IitPDJGr7yQ+3295aKwvt7Jiz5fDgi+rlCgPT/DsD6exI2+q8sIvx3V/Ty5I4u+W0dHPckUmT5/+8q+Qj4tv6VEVz2Ybok+/oF4PiL+Gz5H0RO8ta0OPvuDLT5XUpi+XioGP/UYOz5Nv88+jhE6PuUaR7/cnww+Llo8vQmQ3r4k8Mu9e+omP18At723eVU/PBcZvTFLoD74wkA+FuUNP5KdGr92fbi9pNwqPvLNFL9fASM808lUP/Sh2L72c9y+RV2fvhentT1cc8M+szyZPjaZtT0dMpy8vPAXvn+OAj6QyDQ92wMsPrGZCz/UvYk+tB3dvqwqPzwczoE+xXbNPQgIez3Im6u+btaUvjX8675lE8q+6e/2vt/U3j2W++2+rwJvPcOjKb9ykJ8864+8vg8WCz7Ibp29q52VPduXLj7N49A+ix8/PkQ7c75u7YU8D4PrvtUVj720AG49FYqovpjAsL7zURQ9KbjfvoKmB75n4Zg+9PrVPeVxHL9FN6++l0lsvWMRWD6zEK89i830vjqeGrzwTrS9I4kQOy2xfr5fxNq+GUqBvgTViLzBWsU+MOIJPoOPor4S8Yq9TDu7vT0P9z3WIDI+UIMLvrIaZj4VEwy/R+iKvoj5qD5afSy9wD6AvjaruT1gdJm+8Ap0Pr8CjD7+eWK+/4zjvWzb+L18pgC9lnPuvVkjqL1QqpC9bBqAPjYuXz4F11S9","IF5QPrWdTD4Bgsi+0KruvOvHnr6rxus9h0SEPkHprz22Gue9uQx+PktMab8UhAC/D4ExPxlZPr+G5MA+cZm/PXLB9z09C4W+ZZuDvgb7PL4UOtI+MCZpPvTURr5nRea8Qf4XP5MPsb51J/a+ZCeQvtu0DzxjK5s+Uiu2Ppc4BjytZtg+Dwvmvi+e3j2bDBs/IRUrP6CD4j3NdJA9ecuYvYyFfTwQCo283ziVPYjnDT5DQfW9MHgMPvTJYbu8Xzi/F58av8oCdD36MSK+aQAzPp51Jb8xzPs9TZmIvh+nl76BlUg9JHAvP45daj3Gi3C+gmeqPh5Nsr5mtu6++SjjPujUNj6T0Ze+t4pHPvJdeT6+qxy8dHMzPUPWTj2+4MG9haHfvVckjbyDDpY9ljkyvXeIcD49Dyk+135QPrcgTz50cjI90yL1PbN3RbwRwEo+Dc2bvV/8Lz1nzoE+MAJOvWEyOT4EeRy+Pk02vSSzsz0toE++WcEePT7sur3+Cho+yc1cvU1aIr7zz+m9Tkm0PvKAKDyGT1k9tZhZPfJZE78AF4A+G4ydvXpU5T1TEBI+xRooPn9gCz5g86M94A/wvkUEiz4GrLW93Ny9Pa0Lqj2mPRI9V823voQ+Nz4bOow9zy0KPfaVgz2LZlG90hKoPF/Mhz0nbiA7nqCnPB4zWj5LWvm+Ee+0PeENvr6Hgmm+q1T8vk2yvL2l5ME9S+EmPYFTNz2TU8q94L+fvnIe8b2pXIq7KcsEPdIVpD3FEhm++DVDPFsBI7wCOjG++UjhvqO3Cr7QNZy8wOIBvs+UDb6E+w89FtoBv8EcPDzUT1O82SFJPeHZnr1swkO9HJZwPXxpSD1TyCA9Lxb9PRUsnr4sHXq+MkMyPmDZlz7jVZy9R1LfPU7nsb4TiVY+CmfhvKN7hzxBO7C9l5pyPZuNoD26Khi/aiOMvsxqJ779KD6++6eOPiYKtrtTQUm7ZK1IvYRci76tTuK8iGI7PhgxDj5Zck49FazqPafj6z0FvFw+","41veO/WGGz6a5yE+szZZvVF8pj2a+wW+WK+aPVnkNz4FcUS9sM0Xviaw8r5PhBY+gwkLPv4c4T2To1i9nZKHPX4njbxduoq+O0HZu/hGkL34ANI91H7CPcy0ub6YnCa+q05pvt6DfL5vrfm+e676vQTMgD6mMwI+iAc6PrMRXr1knJy9jGmOPYppG7u0ADY9/bUNPWqcMD7tD469wEodPfA/yz3omkG+RhBmPtF9Cj+61HA+TrLSPou5ab1iqza+ln3SPi5Aar63zUo+SceNPtv7gr6ZBZm90cHIPdnxdz2RRDW9hrEbPkfM8D14/S4+J6x/vpwoljwsYe0918zMvbzpcj7S15Q9HbcxPdhEjbuHhSs7PMWpvcbX8D5oODo86SYSPjXJAb/sFba+6DufvhaVbz4Qf3o9uomOPhMOD76O+Y+9tLQiPh2bCT3aOJE+FpXPvSVPZz37+YG+QiWHPir2qj7CgXk9ONfivTMF5L3yP4q/W5fzvQeZPD7q2x6+b40FP0do4b20MZU85JqvPig8n76YYoY9KV6vPOmBkT0o5IU9pekqPsGwZj4nBoA+fwWwPbjrDj5XtII9qk87vxEvlz2R8Tu98UZKPS/bm74viSc+fiaaPFv/gj7YFoK+e15cPS86JD7aQBW+cVirvs5LPTwayK89LJ0Rv48t0j6yW1c8LvXPPrKEPz7oEMA9kw8SPhqTJz6qy5M9Cqe5PUf4NT471E8+CXaPPlJpiT4eC20+cLyIPgzBOj+7/Fg9XjLwPdVVUj4EQ6m8VKUCPjRzxz5pf7C9jaNEPntFSj7Io0M+TA9YPn2Uyz7vwfs9Nt/ZvI14ID4OktU+jd2KPiMRi7yitok+AFisPvhyo76dmLQ9Oh3ePWskiT7S+JU9gsG0utY1oD0cit49XNh0PYT4pzxAwPY9yE2FPrtVNj1LqIu9+AoAvpDtob3WaES+87kVPrkqwz4rlKc9apBePAmB1z2WhcU90ZJKPtxxu70hZjC+Z5QmPR9YLT6O2TY+","lNnYPWOOIL50iFc++vjoPNM3gLw4a3q9ZcFuvZelYT3RRnY+WpEVvgC9l71EJya+rEI7vXlr4z03zn0+M68DPd/KPj53S+U+VKolPh7kEz4QmsI9MZpFvspPBz6lgs89twgkvVgtBD4htIi+oT+5vdTQhL3e9AM+3L49vXONqbxEHhc8ZtoLvRaYAj9VKSm+xr4avkrSHz2WACk97qjuvKBKi769Oho923J/PuxMjD6zjbG9DSAkPvRQlL3aQeg93hOmPnB/6z52J3g+fOctPMoIdL3E+CY+qJE5vS64sL2+JiG+a96JPWOtKz7xdd89IRBgPcNtjrxTf5e9JYx0Pn6wBD5vvwO+hKWEvG5V6j1EBZG9qnclvlG0Er1Mdt+911R6u81qSr0uamw7Whgvvrx4Kb1lNog83v7iPLqx0D2q9B0+93PPvihXXL5MKSc+aDFJvX25Zb5rto4+xak2vpr8pL1pe0I+Jt6gPrHpHr7bPDk9u8AwPmXv0z0vxH899lbXvYTfer3CpDU9fSzAPQ50qz0IcSC9AazGPmpLUD47gSY+5ThhvbA07D5pV7Y+iyxTvst54L5v7QO+kwCTvFMp2TxByv8+C0yyvSZd1jkESMk93TyOvquJLbxhP8k92jncvWopqb0agua8HmYQPccjp720xb+8gT4Uv/bxZ7wWWsO9t6jqPp2UHD5AQTi+F74EvxE24r1/FbU9qiZEPb8S9D1qWDk+wqKevh2wbr0DShc+TWQsvqhnPD3p2EY/eUmOPoqf/T2ovge/Gl/BPYzIrz4W6Ta+4yZVvxuZUj40e6I9iOPAvcP9e7qRhQS+29qBvkZKCz+pmIg+tnkcPgpqZT+sswE+r+0ivSkSJD4C81Q+0oc+P4IlxD3VxQs/iLIDP8cjij6a+c29iO40Pvf+Db5rPfU7Dat3PZ9+mb7rCVw+4EegPqalv71RT1y/Ch75PsJ4TD7q25y9D1gzPWuG5j4gvDE+HXPsvRt8Db4SRUc9m36bPVH1N76X7Oc+","QbMjO9pLdr76vbC+ls9cvjrLrj7iBiu/fOyzvRgys7zUhwi/7GguPgVzqj5GoTw+iObIPpfBzz7p15c9/yl/vpAONb0jWp+8GA7Xu1D2Gz7Rqm2+Gby1Pv0X472f9+I+v6doPIBz/zswgYO+OzJ0vkjpFL6xBjU+UzVGvuUYET43nFY8va4lPgoQBr2zaJK+Tz8GPTHd17tbhMC95hgIv6PwwzxqnFI+zbnAvFL2sz7qQyY9IMpRPluJ0T1Y1qm+QCEePUM9ZLwsxOA9tw90PQ69SD5YSGS+r4a9PXvJ1b2EQTK9XVCdPvmXVr3qDM6+4qQNvwN2wr1jICA+qTo8Pgrsrb0LSCM+GlClvt77FL40m5u+ijoNPguNZj0nVS++ugzGvVFWGL2cnl++NatGvsPKhL4T1ge+13kWPx/M0j7nL6u9YtO1vJ7PeD47INK8gJU5vv7O9j0TcRQ9qa/Svi0YL74V26y+AwuZvqUWNj60j7i99YnaveIwcD3p3SO8/WRTvQnOcz0be0G8dtrbPUgu+r0OBq++6T5CPzvxK7/OhaC985flPWBAFz0kkk49RIy4PmKuur7G5Qo++wUYPuuMXD4OruE9o3aYvtsIO7xxT6G+KTk5Pq2Huz2NXi0+/M6APh4ajz73mUa+9nk4PnjpB74wUOC9Of3fvQzbFz6gS8w8wITzPjYTHr77O5c9P/ptvT8JgT5C6Qu+pdvePfFpPj3xRQi9QO58vbxc2T7YKke+NUd/vuOurT0gEIk+UgUgvc+hPz2Yg6i+xDE9vsOjND7G4BM++hCEvuB0+z1DKzS9oUgxvdSHH7878h0/sCuMPXzBZr0BSio9vlGOvpVxoDysJqI9/28VvYkg1D4PI2U8EQOJvg59V73wmIU+sWu9vivucb0+9EE+ZBPDPiJ1B7535ce9e/aTvsNARzybr3C8/OgavSPAMD44mHy+je0LPhrx/j6pRZ09UBOQPtfZBr1OMPY9/4IcPaFczj2M3uk+TCRuPT5VAD+YJha+","YJgSvYFrJT/srly+vKCsPiVm2r4bcwO/cq/lPiEekj44WSw/E+5avvKKDDqmPV09hnc+vsA96D0WAU++zYU1vvtBEL+/4yY+hEsfv/SRhT4JREc+SLgKPlIGE75WnIU+AtdPPiojjT5fb+S+mpOTvXx2Ub0qUkO+nVgUvgxSBb2H9Po+dcB3vfyNkL7d2dg+GC+zPq1gCT8kPPK+3E2xPBjfgD4+g1A+P8UXP2EuIz/FIck+AR+CPsGC7LyZs4a+q9F2vgXYCb5/q8O+UWBgvpEyWj48MKG+zJzAPlfPJb6PjlK+kHZ7Pru9Sb9O32W+cJHQPEeqEL6hOOG9VgYAPg3IwL2NOGi+N6SovWCy7L3dCio+rrcyPhW0gD1nYhI+m8aEPSFkyT0vE1Q83ja7vQfneT54FLY9agRNPyIUtTxkztK9VqoOvdS1mT5rr8y9mmuPPm5UHr70fdw+BSvlPOPtE76i3BA+BO6RPhiWSj7AbsG92lXtvToBkj1Njfe9q3hBPd7oBz4zRNG93SKcPgpePL2B5qm9S0wIPvrAIb7bYq07RlJDvvciR72bV6Q8+G4iPpQzir31Z40+baV+PgmltD6R2po77/1uPRLfn7yomBW96SP6vpsRor3XuOW9JUrYPexVrb0GPZg+3Gi7PnKJ8z3raBq93W19vW5w5j0xFzc9BfqLPiBKnz4REKo+zqACPrzfTL7m2vc9VUgsP7wqJD4rFeM8fTcfvZQopD200Ki9jax6PkJzHD6H9E6+IF0gPnJ0173hEkq+IUYvPgTBx701yCG/r5QTvZvXO76z3ac+g4QKvsun3L6HjbI99p5Tvs5rtD4Fknc9+ONfPe74N71sKP4+vXJpPo9jDrxBu9c+QDWZvBT3B70Z/kW/4dONPhBIuj7LAgs9Jpm+Pq4w2b2EvEE+jQYhvcXrEz7yg4A7d2lrvLljr75QIoC97f40vX/Hb74O6z0+YzDfvokB2r2xdwg+WRW7Pa9x/b6+5Lc9oUQBPqZIwz6z+gy+","z3ChveG0pb6HvqU8s7NTPlkfUT6Kzdw98xq4PYZ5kb511o2+clfHPDyj0T0x89k8l6tQvku7h75M/F++TCrsvZnufj3FPjo+wzbyvayI0L66kXK+DJUUPahlIz6N9aM+mm4xPmquRL4kBwq9ytGIvlPUET6+hNw9DTHmPSbEob6oqBG+XerbPjf2fT2w1UK9rEtnvsVLkL5DLBW+cJ2CPueNEL5G4+W+q6FtPoATAL2wJmg+kohDPtnREr5+AGU9jWmhvaKHxjxFoYm+QBaHvmVgmj7lgQw+n2cwPvD10j0Hi9A8rAJ6vYE5zT71JXG+eeekvdonk76aN9i9Zq4OvoiaB72bWey9gZI6vjtQz71LHTw/IFdPvvGG87p3Rie+mpYFv3LLnj2peN2+ldrDvfaAo74w3wg+mz8JP1QHFT5A3hy/kixPvcUIdD+269M+pzGEPrUVnb7rTSU/I0ftPZ8EIr4JQbW+nDK8vq7+qz2IKLM9xvENPfrNmT0Wxii+mC2mPjAxhjyAMnm9rddAPtMwwr5GOxI/92ulPh6BgD7gUp8+TLyHPtP1Ab583mS+L9UDPpg+DT+vE8685tSAv6pMxD5sCCq+9FWIPfGu2b1SU1e+rDU9PnqR7r3r9Ks+1EFlPvwCKT0m5S4+btn/Pv35gT5QwnG+1Bv1vqI6eL6YOBK+Z1DsPhauU73645O+6APOPXMmTL2bKSA+gFacvi1O4r4DWZG+W9Q1vbgdFb4aAte6uS0EvO/zNb9Lq5G9x2BWvYAlh7w+Ql8+05DNvf50FD6y5wi+FNMUvXG81jzkMBi+Cgtfvt4Hkj2wZBi+oEONvodlir1F4QS+gckIvrlRXz44PjM+ZWeivNkVar6Cxwo9nVTOvEPnOj4jQTs/KztRvgDroj5Ye/i87gc3vjoFob55gnu+EnUvvudbED49HrW+eirbvbxXTb2D8Jm9ra2EvNoZCr7GesS98zZyvsbrzDu2I/W9mLTwPUIyGz6FXCW+1sYPvZHHCL6mnsq9","h/08Piofdbw1bD2/RSQ9vkbb5j2l65W7Z3idPe81kL7wEpw+h8IFP0VU4D2XeoU+sWTuPffhtj75SpI+PGRIvbzIXz7+8QU/wGpXPcr5UzxiLSg/LW5Qv8O7sT53+Ew+mdcPP9VFj77a4zG+a6HEvsimfD50nbk+ZPhZvaqJgz4QHoS+Y5hsPJfPH72y8Ci9sxTPvSXeTL7K2cK+9vvXvkQKhDzNUw0/2v6fPQwNfj74OEU8z7/kPtD9Zr5JkH0+OZlKPmdYSr6P76A9kTOHPe2d5z0fL6e+x6M/vYC0Zz7+++s99E7JPUUgiD03Kre+fHKqvikvzT6XxZW+L2ubvtHRe72k0SM+mckkPkG1sT71D2e96fYnPm+Nfj7SalU9emNTvo5pLT6JFz4/B6y+vawCTD7Ufdu9AI3/vXh0B7xiDfO91/PXO3kG5z3r2P69mJCsOzSvPr2uw8w+luG7vX0WSrwCN2E+WGuyPl/WEL5Xqc++/0+FPhj/N76IBic8nC4DPvAJc7z+sOI9CzD+vCSioTvvAIq+f48jvDNTOb37u9U9Za7/vkuAo72GGZ6+FxesvZUprT0z9Eq+jg49vTggdL7kT/y9IN1bPeHpcb6Tsvc9dT0JP4mTFb7GKjC/In1tPbMFaD04QBI97Kg6Pl/weD3U9Z+6UtbEPgttTD0lMAW+p5qkPcehbT2y5YS+jO4SP8lq9z21JA2/0t54vNhvC7/mBH++SrX8vqKZ9D12Pee+/9A1PtFxUD8dwpc+v1cnP9yIMD3OYwG/S7jNPhk0ZT5355m+ESqaPSLTE7wzAo++di8Yvh9usb5WpLu+/Duhvip6FD8ZVQA/D018vl2Otz92gRK+srQ9vZx6ab4pK86+5OaWPvHzZb/nju272p6UPeKWWb1f8x0+IgyOvlW4kr5m/o4+KOCevVGK8b3mPbi+RE1CveoxzL4LvsA+K7tlPiS/sz5iLeq+uIz8PuvD6j6FyYM+Kkc3P6UMKL8i2ri95osKObOolr+lXBi/","zH90PvKIcD7ySnm9lNxJPrJ2Jz5YUXc9KGQKve7vYD5tBRg+CcWNPQVemz3iP3o+DtqIPY6Hrr2jXR+/+TOxveYrMj0CFxO8q8qPPVXwgD03eWM+mYN1PmJqmj1bXBa9qEOQvb1NWT4TWCC7vNChPhi6uj7QV7W9e34ePpRQij1hWjm6LSeGPWaBBj75CxY+24YePavtSj6uWEQ933SjvL0SYr19Tea8gOsmPv7JMT44ONY9bDgTPi3JuD27K04+lRAUvXsvzb1/0mg+SJtXPWl40j1tjgI+h5DQPX+AOD5MnaI+ScWQvRlXND6maDE7xnB2PpL9Ij4FnHo+DiKBvhvMQ75QuBO+A05aPhBwuj578/E8UCutPXo7JD57UKc+K3Eyvep71b5vACy+T/z7vfvyDD0nKQ++6PXEvHgPrb267Ro+0Y4Ev2kXNb1kQ2q91/wQvcRV2j1ljL08aUAeuzMwCT+4gg2+9zaHPqX0tr2elQ4+BVh+PklgBb1dYTK9A7r5OygDvTyf+T28MNB3Pdyk3z74Gaa9vdiyPixEIb5o/iM+wIyAPk42+z0fNC89Hp4ZvoNx8j1Wzpm+Cee1PdVuOjxaRTQ+PUVuvpoAyDy7MRy+9WCYvbR2Tz5vpNy9KEpPPilKKz1dmGY9RpRgvi07AT7wB+k+S8PSvDqj973sRtu8406hvZu0Kb//gG+9MtNsPi04/D1shtQ98NbOPPtNQT2ZfmC+jnanvYsHRT27f5g9KV0dPQI1br2JQ/I9GOA3PveH/Dt5i7e9Y713vSTcM7yjl8a+lLgOvPxO2z5hwuU+wWu2PWViJb5eRAw98vdHPfCqmD47inE9kTq6PeO2Fj6NBA49/GzVPZFGDj7shby+YX6/vMFroL3/hJU9H6hmuSiUtL5pqy4+wKndu6iJWD1ghwc/dchGvvF+T7x42lc9WbdGPqzygT0COZc9ebUuPPLpDj/ZQVC9b5LzvIwwmr5zRLi9NZz0vWKedj3BHMI9UWGRvp0rtD3V0LU+","QfuAvTw3Cz6US+q9pRzqPGtPNj9LAkG+nJvEPlW1ij5GJtM+3LU/v7HJ7r7riBc9X2qjPsrQKT5GKJ2+px/XvfVZQ7+zVcC8aGHsPp2eer1QrPe+mVWxPjD2CT9BC24+rUGXPY3D3L2anr4+38wsPzF5TT2oU8C+4lH4val0gD3knzC/LfWOPPe7Er5W4Uo++2+SPm0UKj70yuG+jWhCvEC+wT1MUCO6pSZJPchjDL6KMJ4+e1kfPvpAKj5WRde7tXAgPxrj970igtM9YhbgvQvlGD4AH6I90NiYvaHgxD6GaTI9WrFOPq1Jtj7iho4+Le4wPR0HD75I8eE9PRq0vkM1DL6rkrq9LvFzPhIGtb2E1IU+l/vSPRIjLDxYkqU+6JVfPZ6omL5+9fk9swXovjw8IL/AQx26CVoCPXCI7z0Y54m+qcWYPRONwj0ewza+9V8EvwqESL5qF78+xaSZvKldzzx2Caq+qrYiPunoLD879Dm980eLPYqyB71i14k8zJFAv40ijT38N7i+GfecvtDOvr2kKZk+3D90u5rwj77tRZC9YP33PhBlDD6ynso9ZcMIvVteKD64fS491NUOPbjLW7zD33Y96KyJPUTRf77P6a49PhxcvnSkbL3BzR8+w1a3vqBxfz1cVjc++/G9vgvFTb30Yga+TcAavVmvmj3XPAC+MYiTuaDVpTxP6HE+3S2bvnJMp7rlboy9Y2gavv7kfr6x0A4+7++gPDt0Dz6NxrG9BMDeu8ljTT5ycYA+3knIPrR+cz6Rsb29reg6vorbK75TXEw+6kuAPrn5kbuDZgg+TZhtviYLOL2cZAM/Zy25u8ziK73Y2RU9cP0jvnT4bD1qCma9DOJ9PoprE7/Kmnm9zOgePuOp1j5+x1s9pqFIPO4dRr2Cita+wcTqPTDMhr3ZNYU9qyE1vqJGXz6bLd87wAN2vqXYLj1vsco+F1rmvWj8sz4IL48+gZpEvh9EIb0Qhri+Yqn9uxbGkb52Ym8+6C2YPdjUGL0szRi+","7D9bPjLPjD7bmiM+dBOHvmOc+b7N+z69vslCPPWqpD39Iyc+f53cPUGLBL5eMZY90lxvvXCjjb2xzQU+C92AvbaUjb1graS+ZdB9vXiZdD2S2+Y+BzarvHqohz4Br7W+1CCWvdyABL7PMyQ+nWNFvqc4Oj4NB629iT6bvZ9nrT4CGoo+cYUWPjLxi71ZQ2U+HpxXPY6p4D1Bzta+eJvOvAxmQj69FXm+jYZXPqebyD5KmWe+Y5tZPpOljD5CeYw+iwKgPaIEh7zr/vC9cBZYvFN+d71Ap4G+kK8YPRgmPTzrIu4+UpyHvI42wT67JLU9iOuTvpPWsr2dAAE+2jzlvQ4Lz71NA0s+PN9zPqVcX7/MIA6/IlU8vhhazj4KLYa+cCxRv2m0R7y6hjy+QjQjv5Osjb3K/VM9Zd0HP9tP/j75hka/OWK1vuD5E7/qVVs+Xg2fPjXiK78bm2a/rSatPs1ztb79SP4+sv/4vC0tUb0+M1Y+kwZJPlt8IT/Wp5k+7NKZvmmvjj/49cw9IjP7vozTpL5sbv09Cn8oP2ahLT+HQCm+DhcOPuNTrD46uz6+9GBMP6RwlD1muiu/kPt3PWENFT85Qta9HVS+Pi5lrT6ndBa+eXnRvvK2Bb4cNAu/d9zmvsVmn7wlaIQ+Fi8Wv5YeGT/y0Cs/q4OGvfeZ3T38MLA80isAvYvF8z28hcy9XZ5IPiMNrT2K5Sc+tLaYPLiKsb54AiK+fI/ePJdrujzdohS+aadQvn89Vj4MWHQ+ZPqSvS3etr16/XO96nJCvbtygzxBxma+usaRvuyXFD4jMr295Dj1Pv3wJb6nNRC+mQ/YPB2Tuj2bRZU8dCgbvRDqUr1sxOc7vhvlPvVSCL/0Ums9hTjruuaPLT13eKs+UYxLvUCze71Pa5s9+cbnPU2aFb5hhp6+cC+ePbd4W71YkAS+eRfQPNeaCb5UOU29hmMKvWa7Q75dOaa9+sQ+Ps9Lgr2H7EO91bNTPv+Hfj4K2Hs929GaPgamLD1j+Oi+","c3gDPtXWPr1RZSQ+mDz+vZFk071GK748Qv+Wvmd6Tr5o5nE9eeQUvhm0br6uu5a9aKmgPuj3zr2DTNI9z0kjvl3sIL1DhJG+nkCEvnP0BT9mPVY9zJJePaX0ZD4kAZ48QK/Qvk0k375Tvdc+lsblPiydgD1lgVO+Z/ZnvUnxwr5BiK49UVJOPGlvAr7/xqw9jWafPuubmLybYh083TfEPvkFRL4EGIw+v4Nlvsx+pj2pBy09itFfPQ1VA79sorI9XRPgPUyJ1D0SrLi+gNdNvZNhr76aJ6i+Iub4vM6vj76Nofw9Bqp5vTUhIT02dNo+o2bpvTnZ8L4b7R0+joH+PSILTD6QIxq+jTG7PU+NWD6wb2S+A05iu8VShb3A2gM+2V6YPE+Knb23Lbu9HhIFPmFWjL3F/Wg9wrkxPjDegj64plE8axd1vm3mVTycvj863KWGvGibgT4KtxY+KLd6PnLlxr0V//q9ewiXvrV0hL5PPOs8L4Fyvv7T9D08aUC9QTOmPg+wxb24tK69tQjaPdoIaz59wN89ujLTvf5trDzD05E+EjHRPX8CAb2XlTw9UvqrPfYuqr41cIi+BSkGPG8LPr2fAAs+txIyvjgt2z3nBhu+fn4hPt8oNjuiJ1I+WgLTPv0S8TymTBe+f+Y7vHxcOD7RAt89wl1dPhW+nT6DTu+8iCUwvV2t9j2H6G2+IfgIvgA9yr6jirI+qphRPlKgHr9Ygd4+LfblvrrB+r1vzW4+iepLvjgX0r0qpKI95hnzPUvgA761TJI9uqlCPv3Wqz7nFde9LsXMPsFoXz0A6Au/P/U+POGgDz4d1wG/EpL8vS9pgr5WXAm+Ub+tPgy9Tb0AJqi9Aw84vqX+fj4Dws4+9S+LPZ/4XT6RL+O+kGNGPXyFSb0IMW4+Hw1WPRgZkr/J89k9T/FTPvxjuL5mYhm/+/0jPrmziL55xFi/h7kZPg46Vj1AN2u99IUfvk8sWr3v5wg/SMbzPtTdvj0V3c+9mqurvnWodT/Uohi/","Z59kPuOJTL0jFFG9otMQPoUD2b0qeyu9yVERvq3IiL6HS7O+AfQbvcwaEr72ql++4q98vjDyBT62KUc/21JTO35qV7tezHm9+9QYvnPq9Dtk+S89ASl9vbkYlL4kDIa+YAElvNcvST20WCO+qf4Avz8RiDzizrs9qalBvreDJjushcW8FXK5vRcFXL6B9TS+2yKMvsBZPDxEZYK+3JBlvnMJNbvq+yg9dvB1vA2gBz7vfFK+iM1QvXdKdr72Az69gr8vPs64Tzy12eO9gULiveqax7zPMw4+CMzZPTBOgj0qpcW+MiPpu6lzDb1IYlG+QFWFPI1pej3x6fu97HpxvhclvD5+Qjm+jzoGvukMHL7E6427Z4xOPmo5gr3thmU91F5avjY86L5If/W7/JOCPeY6ML1SPVi+LYnlPZECFL7fd9i+0nGYvt1t3T3azR0+xG8YPkjPkz6hCLa92vRpPBNTe78RsYs+DjZAvraJ9T3V9BS7nATYvjYGa73kepM9LWoSviynJrsn1aG+Mju6vZpMOros08A8UTZavqKdgz4ptDe9vJFEvYSZE740vB2+30q7PEBBQb8Bnvw99JLAPf73KT0WU7C+HqENPSwK672JiXu9BnRdPZ20F74Cf3u94Jz0vhw7Ir1TKH08vVBSPjbjg75wrKW/RYa1PqoJn7021K+8KvzdPZqDRT9xBnM9vgm2vWcglTzg8o+92FjpvXRo8DuEnpc8rSstvnDYgL0EVaW+slboPQLkML2c5u48x/MuvjQTHD6MopU+IQq4PpFNz71hqh29sE5ePqCUvbxd9oe+wiwFvtxDaD5RjeC86Uh1OzAnmb5xvkO+d46ivaiUGz69AVY8qPy+vRRnyDpA6RW9YEq2Pc9hczzRYwy+6ASLvetyzD4Kfou+6uxQPKx2WD3jlGS/ZSDOPSElwL0kQIS9++SgvqRKtDylOVY+ljMHPe4fLL+GoTC9AvcNvWA1ET9voxO+ZP0mPgp3Aj7TlAw+KFm0PioZmz1H3b6+","qKkBvUaAR76PoJq+nD0FPmjAjr8hyZk+xTtfPpGe272sRM69Hn2DP/6YCj99+DI+JZ2Bvjl/Nz5PmSa/FXFIvs3FAD80W2s+TFaBPq81nL3FEmC92z7rvTnl872IEYm+u3MDP6CLLj4UjvA+DmQRv0W9dj5i5dK+St2uvv4yiL2uXES+68d2vtuoOj4nbCq+1paiPPfryr5ONAo/rw/XvjgG9b1RP1e+WNGnPmJmY76CvqK+DOeuvun6Ib6MWEM/PjuNv3vflr5GRwK+mczRvnMV3ryUKkQ+654sPSTb777DhIM9ws6Nvm0Lu76OLTI/RZGtvuwiBL668nI/W8GcPggMJz7sf0M/jjWLPkbPlr6JwAQ/EYADvkH/Tb3E7KA9ZEeDvmEvcr1YLVE+4jKYu5axk74mszq9ottOPyVeBz3VytI+mOiNvPZ2BD7hubg9/FSNPa3EEz5yEfC+vHExPOAnjT6VAjm+NnUdvRdwLDy/yGy+m4s6vsjtnr1AJ2e+z4WAPbN2vz7bUDq+7T4BPzOyxzxl+XC8Ibg5PBlMi768SVE7EBVNPX7jtD0g/yQ94BsKvsvOQD5IFl4+h8nGvQr5I7xLdOY9wN/JPS++wT6nYNK9ZYagPtEFxT6Zjqg9FFBKvsa+hL6TJMQ+SlG3vk2c8r23mfY+fIcSvWs+bz6C0+s8CSKKPmN4oL2Trvq+AgApP2BzZD5e0aU9CTTvPemyKb7E9xg+4xYKPtNafrq7YPC+qRkXPuvNUz46dYG+UEUwvm2VjT7vvnc+vSYOPt8i7T1w9A8+sUBbPhptYTv9vCC+wXO/PmXAzL3NdaW+blIiP75JaL4/7LW+2C+XO+5Uej4cr7y+tHl7Pn8KAz+2uQu+AEMKviQNG70R67u9M0mtPvgxOT6AKOU+4rfNPhqJJj+6mWC8ZEbzPp9hJr305sI9MUxLvW3oyj3mMU++vE01PhjPDr/WHmQ/+asav/aC776bILE99goHvuS1PT75r5y+cip5viUegr1Qa3a+","7x9xPgTKfz74hca+qXTdPn2cN77AT6E9c9ayvYRv77t1wTe+6GM3PSldCr64lmo97q9nPQY9n73bjve9b0KjPub9Fb6ZyRG+yk6KPsvQaT1iJI+94qM6PrjDGz8/Zba+qN7evVTOjj5/5Ss/Ot2OPpRuGb9CzEC+Zo9CPh+vm7woKNW981u+PUyrB78/30u9Zx6/PTLwqD4kWAw+BsPtvSZ6175e8mK9KFCsvSEabb7Dozc+fudPvnTcRD1Asxg+KtODvrqMC74AcJm9CU6KPnYqB71uaFu+KNMAv7lVpjxVelY+Ova8vf28Br8zwoY8UwTEPJilzr1RDR8+OBGePd0kLj7JB7M+NywpvwzIiz7yazY9xt4CP8QlCL8sHBs8ILU5PgUklz5OJs46VO3zvgxwwbvxmUI/AuqTvVPkez6NPxw+LE9cPsfIUrwkwWQ+hTacvqc5Ez6y2+4+gMqrvhQgpz48pEK+9N+lPqimWz5AVA8+TvovvRWRDT8MFho+l5PaPmEB8r2XN9Y9iNZ2vl9YL75W8QO+6+bEvf8kJz5G+gW7gLxQvrH8CT/9hgW+zjaCP5DB5bpEsoO+HWO8vbAL+L6ZmFY9myPwvCSuW775VtG+/PaOPuu2jb4m+RW+SKXxPppCDj7RooK9WbyPPtpFU74V5Sa++aTqPRR/iz5QB9s9/wkVvo30BT4FDCw/23wXvmnnVz7QlR63yMWsPhXJcz77L6k+OasRvoOEAj5R1Os9Qj6XPa6H3L1pBjw+czkhvaLL7z0D+QG+wnBwPrlvhz7VvZy8ILGqvTnGQ73VEF+9BJfgPakk473UvbK+0rugPiJzpz3i8Ko+3QNKPNV2nT2Jxre+SJ6Ivoi/aT88DXW+wP/oPe/PCT5zn7y+UIxPPdZQAj5PnVm8YZyaPsu6hj5D8kI+Rg3SPZoqp74QQjE+iLSAOzYTgrxbbxm95lnbPKDplT4OWYm+LCjDPe6ztD3tSaq9BDUtPsih4L3u93o9EidFvidFhT6gtoa9","2pQWv9qbVD6XiOA9N7tLvd00kL7uuxK+tKyovrTRJT50OFs+twBFv8Lakb75G7G+fSKFviWwWb55Pr09utkNvloCjb73BkS/sFskvQ7/Sb+e5OC+aEBDPz9hkr5ksUC+JHmNvqYdtb4YCBu9LEkoP5LXqL36Fo++TK/gvRB4Ir3oheA+HU1NPqNTTr5lLja88/qPPhK1MD6CxiM+rp8EP/lqfT7BfKq+6MfXviX+7b6MmPa74HqLvh3hEr7LIzk+0iLJvhCYgr0oML499L7XPV6FW75mGoE+PhnKvWhgkL4WUH+9E6Aivqjgjz0uhT0+j/CWPQmfsL1CZZe8FxVzva0KRD5vJUe9MxwPv5PyIr5bOwI+2MctvlTqN75Mjjm+MBvTO99kDb4ppVK+TD/SPkoKZTuOys09ulbTPHZ4MT0+zio+I4UOPyKofzsFShs+6S/vPkgVfz5fmh2+plOtvZORSD0lZxm7mdQvvxs3db5jUZ8997pDvhK4xT6hlz+92QiGPMp9Gb2UWXa+0S1QPnuDDT4rfh8+yHw+vIfIo73zqyu+IDeAPnScSb3Fv209J4EbvpMTDD9luK89gqmXPZzC9z0/vVy+DkAcvmApXz7IMZy+eJTJvvbh1DwLagE/ivFQPS2M4Tx4gna8CCgMPpurjL3fz5W9VmEXPrw3JL78XrI+qdo+vhbdc76ffh0+/8WpPjApWz2iilk/gN+bvbvgPz8GHQ+/KFE5Psxz1z0BBy4/1fYfPgY/nr5xHde9324Uvnu6Pr3VYko/T0nVPST9gzyN5+S9nrQsP66QLT66kDE/V5eGPjT6dD/7GLU9pZaUvTdXbb8i4JW+LLrnPeN4Sr/V9CK+hzzPPIjc4T7319Q+UFQwvbbypb3VpOm+yoMaPUBGqj7afZG8+2CRPrOjuT4sgbY9t2ZuPo5nvb4+y9w+9guGvCxDtL2JY5O+jvSevVDMyr18wzs/ZgNHPTx3HD2GLde66DgBv9zVtr8+VyI7+f6HvYzIpz8x7We/","RGmRPUW4Mb4yn2U+9N0GvmHhJT5KxGQ94wSNPR6utD3WyiU+7zzDPvLj8z5Y5Ig+JHEcPvgMzj7ILWM9ul/DPnAXDbzMC/Q+rOzbPQ8Pgj4rPVK9vshcPaNubT4rXMg+OarTvVELW72aFF4+0inwPLsn9z4But09MgEsPgd+Tj4A9jE/CpPAvt7vYL6Vv/s8NqvrPB0dAD+4zQI+mybcu0vtZT5mzZc+X/iZPpIEIL6yTKw9JEORPt2MBT7BvCS+TwwzPzWXtzyJuMO+7xnmvSazpD0EpoI+gbp5vSFpGT6XCLU9+mtaPuIZr776Bb0992ZMPg6gTL57fvQ9PG+SvWCxVL7OuVm9/Pd1PXOtur4Bn4U8S/KkPa1Imz7KP1K+kmV2Pua2ur6NuIy9/e4bvsvzxj2JE4M9EwgMvuxmlb5V5Pg8eq7dvZ4TWT4UTMO9IZDgvnAGkz4cPu89BHnJvUVtur0bH9e9PFHYvv2AP76VUla/nw2BvQ7NP7wz7C++KC7fvr7uNz7y80o+v5t2vtNUCL7iIm++TBb9vv4bKj8duCM+FBSFvmISsL7eWLu854R4vu/pTT2g3XA+hg+1PmqtOj11e4g+sQfJvlqFSr9jQQs+WI3QPsiK3L35Soc+1Z3GvoXKAD7eQLs9wLdBPzyyEb4kVyC9rjP2PjVu7j0lFNQ+W6CRPb0Vhb7yuGq+BTlePe4t+b6+sHa9Atc0PphEBr7bXk29VKlEPLP6LL1HEaA9AirXvXX4jT2nFYQ9YwplvRRwu750/lg+ImG4vJuWM71Ftzo7JjcxPhyLAT4dJfO9qpCCvsfvC76ensc+GkQPP2KqO707iXs+f79Avm6fBr4sxwS+LqAjvevC57zAUpQ93ZKBvdUkAj5rNti9DagNvUREN72qE7w9QHkoPTLkrj11jCA70y5hPdWwu7wFgDE+25fqvZGNP7yyZOI+BiJyvgq5nr4IKUE932opPXBUmD5scbk9OsowPj3MrL3d+ws+vH8vPi0d0b08FV29","j6E7Pe/glr2/WxE/Q7f3PlanhL4CvlO+FYE6vmjnf75z8cQ+ZQUAv5iGPz9Bq5Y9SVomv9oFED9+7PG+WEERPj1iM74Nodg+M2YNPs8ntT4uxS2+0b4APrU+O74/eZ0+X38xvuKoY72dWMO+tPGOvSVyHz0MELi+w6jiPkmPpL77X5O+alvAPpkzGT8jSwC/PusMv7Zmk717TFc+sBuLPs2dgj2U5Io+zuK0vCXxuTs82KM9ZJ8QPtKP/L0VwSs/V6QavL/Smr1SLJg+63TbPos4AD9tvjm+Svj3Pqm2Yj4mib4+7pkXv470Ar+3GZw9fS7hvk24wD5QrVa+VIUaP5WpuT2VEBW/n6vNPC2IDL/tw0g+FI8JPn3yBL3B7I++KrSNPhlBHb8yll2+HX++vmdhlj6LRNc9qQ/bvmE9TT4Zn7y8j2NnvXsouT3EQJC++zKXvSiJgL6Im7A+DjZavYqy9z1JbwK9UPIiPKNLBD5Qz9i8NLfxvUseh7zw9Si+XCUOv90naL7jEOU97IFcvvCmHj1wb6G8zHUFvvpHcD6HxNQ+r5RrPk0Icj6hApc9pp6TPOZTGr5gsZ694k4FPgFznz4Ii0A9JyY2vo85+r3UoNq9/dd6P2Jjbz2syQs+7NJwPtdAOD1YZDC+ZLpBvcrBqr1yBwk+jQG3vr5w8L1eDc29yzWLPt/UTj4e8Jm+ca0OPkcOVj0OX0a+VLGUPDOSUj7UHCA/B8PnPk9L0j4qZAO/xWAqO8haf76eoAe+fIyFPvoivD0fud09xH2PvggZib7E3Je6cAoNvgibdbzkuTW8TZP6PcmisT3yZXy+X3FuPRN0PT4124g+N3YBPscOQj5cWgy8ItOyvv4QFb+aAs+9e6sSvv9fcT3fHEU9X2zoPEUba7/su66+Ej8AvgTfib6N7Lm95Xs0vsFSWT3TmMu9GQX2PXZMSL4D7ea+R4+vvDRyUj9xcbW+Z2ICv8p2tz2zatK+PrmaPviQIzyPiLc9rN0/veJEzD78oZu9","NyhKPm8qmD2UvI+9mkKJPgU8Bj8QDhS+rklWvOBAPLwVY9Y9fPquPvV0ir50rbM8S3TbvZFpq73Eud897Kp/vpkXQr3zcfi9KiqOPPMrRLvhRWC9kcQnPmBFur6B3aS+G0Y+vRqFlb3rhPA94Ik7PpBhJjzlmRm+E9L8vehyFb2LGHe+Vv8CvQtZtD7KSdY9te+3PZkvSr4n4Jq7kWiUPZGxAj4eoQe+zSTyvb8meb6tAj896l+VPb+hzr0KNPY9rs9sPvbS7r2APdS9Ph53Pj/cQLzYcLi9q/StvgUzXj3HfkE+E89uvcAm7D3b3bq+v9/Cvu3unT2R8hY+qlL5PfGdwL2iS1W+o3/JPjTtE71seZe+zbOqviiUDT5IW4M+6QeYv8vAg79Tt3c+9G2vPqnisz3On0q+6P6FPVjtoD3ZqGu/iQeoPiczkz9IRyg/vvTZvWDxBb/nuYK/h8mivplBIj4Ea0Y/pEonvkf59726q7+8eJMGvxAt5L51hqQ+NsvLvLKkLb7B/1++AgUcvrUoCz9BO1++tKmuvYfk7T0nEoW9hoZDvnL+CL7GYLo+J4qCPJRoOT6CqFM+EYBYP7GO3L1BYt291F2cPvQwXL77IQk/xDKRvXFm/rznqhK/hhaXPsJtmL0n1hY+cdWXPHJMer5Y8YY/zL0uvhWmQr8Znoi8lAAavmFikb2tTYu+LChuv927ir4Lqau+nawjvqlGiT0dEIe+5qozvSjNEL40Ur86fJu+PNPi7L0i28k+e7h8vszLjr0kl6u+nH9mvRZRzb3m5gO+w9xXvuNmCL4Tjv49yYJxPJxNnD68eqC9vPCtPrZ42jzYLMY+skzrvvdmxb5XJfS+luLJPgXccD5exrK8hIWivr0Znz7O54U/yZgbPsn8hb4Ex0Q+GDMLvoHZtLq9I+87AZlYPaQw3D2Mgz8+xcc0PRr4ob3rDra+hxtEvmHjRD6ulZQ97CCFvfXuQD21QC6+5HLFPHNFgb90g9y91m89vGn1Bb7S0ic9","sbMUPTvzsz3RfO292Zkevq4wNz9UqGY+TvDtu0plhD2ILWa+g/f+vv0MgD9dWgc//59iPeeAqj7fmcO+LsQMvAzh5r1JUuu+tYKrPssasDxZ3ea+aCWBPTVOmb58hw0/ZbPLvk0edj4kOdO+pAkuPhpYq74V4Q6/fKpxP+GDgD7LJ8u++2yaPa3K/76RNbe93VLBPdyzML54oQ2+wi5mPzowAL+0d3a9HPFAv7BrHb77F0m+b2feProdKj65Fhq9kg5zvdkR+r29hqs+CYKHPqk2zz4tXpK+CiUHv+DlTbzr92i+HVOpvbLtHb+Sw0g+sAoGP9YPB7+F5/s9hBhXPGwemb1HuPQ9HrT7vv6yDT/VT/a9sCT+PUdl8TwbZ6g8EcUHPiJAnL3NL0c+yOhAvvFt170jUBY+GLUGPjzppb7cH8i+Lv+LPoqR6j1KIso95miGPl0saz7veKy+3W1avgbXTr5SbBM+idlNvyj9bT2yM6Y+8WE5vU1zDL9ExWA+epwavSP6rL10uyS+DW6cPXDkvDvuzQy/uAeTvnGkbL1svi0/7si/Pv4mJ76nMHm+zVDRvVv9pzybksa9gx+hvpJL5D4bdo69ymkzPd65aT1sc7U84MUlPyvzxr6F5La9xEnSPuxDcD45I4e+y0sSvpBh3L57WkE+exz/Pen+Rj46McU++8RCPlLVdT+8HIK+ZP+MvbS3AT6g/AQ+zokCv+/GPL5j4iY9F4icvsIQqr1BmSq948SjPZOn0b6Jk/Q8NIfnPZjtYj3yqmY/rqNpPtE5e7qZgTW+vD7cPkhpPL/pdlo+40YKviTp0j6EgbY+Lw4dvijiwT370Lk9gLazPj+6zL6cMym7uNQiP2APsb4o2t8+tKG6vpGixj4ahkw+1VaGvaNJOz5IyQk/r7mhPWvuvz4h0Eu+W4qjvtkvR7/7BlS9OGESP4DTLrtJq74+tpjCvufjOj6r+J09ponYvkDFrL7sjCk/SWBJPwiTELzdmJY+CSzXPd32wz39tFG9","uqXtPpuBEb/f7+g+MTYqvZhjbT6Wqse7pZHdPnL/oj3sRmY++czXvlGQCb4CQ6U+vlg5v7nsrL3mIr6+HP9VPYensD2oUtG9ZJgpPqU5kz64yTO+nlRhvhg6Hr9wPco99Mx+Pg2bWj5ZpqQ+4DnkPRvVOL3HSzW+uZeePVAJvD1XyDa+cca9PFJFg700KYw6mr0ePgml4L7Fg2E+954qP88K3T2Wp5s9Y7iOvgReRD0OVgA/skEAPm67fj7oiBO/6Hidvhrj4T3sFiE+zFYDv9YtOj7tI9I+FzclvpBhkT3dIYq9xq0pPF78PT5UJg8/I0N5vT3zY7sIb4++NNhJPUEDjT3VOR+9qDDxPGw/0r2eNqG+abgnvunXW71v69U8qqUPvlM57jwvneg+cwzFPj7MAL7SdZ093XAivoaEYz1T/jc+6p2lPnUDiD3Hwie+D43/vOh5CD0+pd69SdJHPWkV3zyQaak90jCoPi1xcLzG5o0+1yR8vYeeKT565dg+6JOdPhblRb3AFfC9to53vmkr2r2wh6I+fNuovT6Fzb36LOE9leytvk3wMru/3ae80WsDv4l6/L15rYG97lWUvBy6Wztx+2k+ss6VPqpYuz6qg8u9vWwmPjEVSj6FUhE+f1Q/vm12671KMKe9tzuqvPbNXz4MVgy+IOIRPe9iC7xWQa6+Z+5rPCTy6b4hqAW9dnuUvfvXTD5B3gm+La0JvjUQlj3ihTA+PqDjvL1U4z0B/RW+Jv+BPWNHez6iRSi+K9ryvQa23770Ohe++iDjPR4nCr5AjQw/AvoQvigbwD0w7Am+D/kaPPa3qz0y3mU+51xjPT0z9LwgKZy+8MYJPu11mr3Efbq9YpUbPv1mhz4/4BY9gcTtutFvSr6BNo++RjgJPoPKX75WMgk+/e2QPeP8YL2B9Nw+bLrzvfqEar0oP2C+EU6/vAKFJT5LGB68o2LkvDDftL659f0+zu95PfeLFD+EXVg+HdKWPfbaOT60cW2+xq7KPq7/Eb6xJwc9","kaIYvshcFj8Cta8+jxFEv9grxL5+wo2/ZcTZPd9r7j74Kym/A0w5vy0p+L53CqA+3T/ePtxNSb9okJ8+2bMCvxFr3z2ePou+xoVmvnpwwb6G9Xw+cn8yvGU77L2Gpwc/h+1ivzCraz56XwO/6K1cvEr/lz+uOD6+WA/jvtuuiL3jE+K+B/N0PorDGT+pvPw+QI+QvuNzqb6pd6u9oxqoPqbmQr6bnYg+7DyjPcdblT4FRbm7xgZbPnngsj6RuRq/D8kMP+QenD0isZk+ZVqFvmXOKr/P8I8+hU4BPSl2yT69nTW8FsjzPfGFHL+ujrC+c+e1vUPlRT1Z7WK/kh76Pq1ATD6fwdq9EwIqPpk2xbz0kEK+wCCJvri8y76KHM094dIUvX3aHT4tQGQ+3w1/vp6cIb8FHZW9Yy2kvqi6Vb1YW0G+oYhXvR3GDby7lj29zc5zvnzEoL5qEga+3o0tvgv4XT6C8jQ+6bhyvotKGLxeLWw8Nn4EuvBvAz0x9Rm+JxELvAU4+j1rOIG+R0t1v42qsTwpb969mbT6PYYapDzqXpi9wwt9Pg9u3zxrPou8O9EZv5yuZzuWNeI9FaOQvf9Ikr3Ma7Q9Sz48PfNVtb3YJMO9GiYMv8sP3j1YXts9Gq+1PpSjiL76+VC+YsRwPlmvsr4V+4g+ZdmNvues5jz0Mgg9Wn4Nv73oW74Z/oq+Mylcvj9hAz7ECYg+5ZtAvz0IKL6oFQ++mOipvJc47z24ilq+zxDWvgW+pr4iuKs8rPiCPSaQHz686f89UK7qPh5wz7wz0VW+HIQrPow4IL7aSzG+fg5fPeKxsz6wRMQ7XbETPj1FeDoKtIc9v00xvgjtVL3hHrO+EJMHvgpxe77owKa+Uow6vc27Ub7sb4Y+RDDRvnFWQL7mQJS9uk6pvJEiKb7vE2096FkxPo+X2DwiNIO+Z97UPR3aPz2Un4C+b+H+PZD0cL6KKtk8gCeqvEmNkDxu90i8kEgPPlM2kj67bbQ8lHy/vnSDxb0KdXQ+","6vItPvyGXD5bcKC+Eu3WPQj2Hz3x53Q+24+rvAIzYD5g+U89pSdPPRN6BD7/h+G9m+W4vra/Nj4V0Bu+7WEYvizG7LpnoQq/MBdbPcM8QT6vioE99PasPgPnwT5wPqO+wGNtvrhIo74DD6w+QvjGPh7jDz4MC9K9/ocHvtWrj77JepI9dcwLviG1Jr4f/7G9kJxzPlv53D4yFeW9hNJqu6itNb39RJE9tzn7vHGtPD2esyk+sVuyvY9Rvr7LHIM+05S2uU7lub0U5ZG7XHu4PrHwoDqLWXS+w3wGvlpXbz1Jsf09NbuAPfX+OL3FnZ+9BjGJPmRgxj2bfCw+wDyBPfSVtj2KTsa+kxEAvSn3Bb4r+Na+xbQ5v+XiPj4bqy4+dPVeOnFgjr+mdDu+O+I1vol897z/IPO8kPmPvsttDjyLhvI+KiaOvv1sM75hsjC9Y9Y7vrxTuT0kC7O+kbWGvVUPtj7G+nk+eChqvn3H37yXcBw/DzUBPunJeb5U41m991Ifv9MfpD6wmQE9JrmyvQ8ZpT532cW9wP0Hv7vfqz4jssK9156aviEG+z2t97G+9jzWPm1dvr0dbs69OsRwviFBiD5WuW4+l6oBP092Qj3jsb6+282ZPQ7Tg77Zp1a8QmMKvCoUoL4Z08i6d+0Mvwjd4b215Oc+WwCqvo0Agj6kq/M9vVlGPt+oUb5xY/+7Y+YgPiuZJj71cqC+EgJrvr+1fj5hxFu9wtCEPGsyvD0gMx89vz8XPZTCJj9MNlu8tOi4vAp+HD4BjFg+ZWynvHCGRT7WSUw9YPGePrxQhT60JRC+PT01PZfRKz4sJDg+f8KhPd2DGb7qblM+YBYOvQiRfz35Fhq+r8q4POoKtz77YRC+6mK3PfFfeT0tPwG/pKPmPZUJjr0m7kk9mlDyvUnr37xNIZA92c4KPhgqaD1C+dA9aX3RvGLqkr3suq8+QnE+vOgK4z4a+aY928ojPLLNbD5yEAa+dnyHvPOHgj1O/va9QSdFveBsmL5tRz88","suayPev/JjxOP7Y+fvBZvoddQDorkYs9cNDgPj7Fmj7xc2A+Hv84ux6klTzqSnE+zoIOvtR7JT5HvUM+qxhevdKDqD5uGNE9jQ3APNMHlL7Y40++e7dKvkgQYr6bVVO9Jf9cPbg5Vz8J3/6+mIqNv+bFCb5I9U+96VmnPdytXb47ltW9fXykPtAN+bzN4yI+6fCfvvq43L3wIZs8mYRHv+ilvj27n9y9sserPba3tj3n8sK9qLYMvqDhJz7ncPA8ngXwvXTRpT3zoso+G6Utvndjdz59cJa+WmCXPDI60D4jz6y9nBWbPo1oST4t+bu+c5j1PQdwpTwBTvU+q6MDvhw83rv6pD0+ufODPoveCz5Ri6M+0d6NPXkurD1pmqq+gNGnPC+PwDxBrJY9jmMKPm4wNz7nfQU+jbktvgv7ab0aSPk9Zw2YPjLKwL2Gwpm9NrlsPVOpoL5BMNg8qjmXvQZ7wT3hXE8+c9pkPpy3BD11dIU9rHkSPrgNpbwrTbq+Hba/vfiG8D4QrFS9uniBPUGqO75eqpy99ZW2PgOm4D40sLe9CCUHPg/ghD4gDEU9qO+YPcIPML0VSEU++4JEvdJYAz7I2ds+wFe6PQrWJjzxkQU+RtcXPuMUXD1vVeU8P9fiu8Yik7zapc89KUeiPnUisLwdfRa9tDzOvkJxFr77J5+9toGIPp5jeb6b716+VgB0PpO/AD8J1LE+Zo9JPZlWkD5aWIo+mYD+vn8AHb5jkSS+NnhFP3i9ED5BHwo+P6X1vXg0zD55W2Q+8OhPPujLqL5gWAG+auKbvhab5j3ypD0/OqJFvX1A9r1BDxO+CjWjvhlIKb7y6FG+kUBLvZ55Bz/+OFa91v8YvnAb7LvWjFq+MROsPv1Onz0pKq09gY0cvm4Gmj1FIs8+66sSPh9WPT8F5ek+wIr4PYvdMD1MwUo+xQ9+vp5TGb4Ar5U/OWLwPu0hwz0g2jG9dXe6Plc4Dz/UWSU9LGZaPcNW/j7Wvhe+otbZvNkkzb7hNEg/","K/eXvRkd6L0QOQw++KR7vAPbl70R+Ge9Ev82PVKsk7wYUOC+KU6FPb+AZz7SdP29ZKBQPidnuL5U+xe/zqTmvSwWej1AFh27Mj0FPOkb07yrSaa+A+RKvlhQzb2eBmE9GHqhPTvui77ztIs9Dut/vUBV0Dz2wpy81BMZvlE8Mj5kBaK8XIPTvGr6B74ASgA/wTUbPhN1B75AGVK+LkBSP0sVPzy5VRw+JV9bvbZgUT5dwR++gcEtvg4dnDx5pG88pXKAvtcnVruC08M9xj3OPU1ZLT5iwwK+3YvivZ/ZPD5+Y0k9t+OnPQcKhj3Nyzo+Jbu7PSFMiT47n5c9cc5MvZfuND3ASu68rPBMvrtYIz5RN/c8mGc+PWdWAL+p+oC+2hZEvtuiBz6mds89f3nPvVBj3765sSO+j5TLvjs8Qz4AVrc8wHVUvhXAKL4NXoA+6pBkPkr4Jr5vDZG9RbXaPHo6Qr59Wua+UqOUPsx1Jb4nvWg+p1YoPhz2Jb780Pi81QhqPmDd/b0QGhy+0+CfvXjSp73j1589H5daPblqRD69Z7K9Ss5gPmT8/LuWd7a+bMvDvh2KgD4+LU6/UnlDvkxYLb5xhoO+qaILv3906j06UQm/24q/PUpNDz6kWVe/1yrbPJlbrL7zitw9KTC0PrAB4L0uMss86NuKvvbNor4J3BI9QEElvYaqW78RLBE+RpoUvkC9p708m6K+YG7vPf4eWz11cgk9Ddvqu3qVXL3nFGm/hlMwvtxFKT07Tak+vtiHvp6OdT7YKhq83jblvRmjir2a+EY+0LKKvSQXpTq9f6W9su6Cvr5y5j64Hik+zTZPvUNf5r2Lk1k+bjacvXjIiz7RGIK++GC7PdrQKD4JieQ9/hfVvEXxPL8R7TK+zGEwPHLNmb2/Df2+0/QFvpadcL6LYlq/yR4CPkdCRj4/ese81aaTvip7t74kOAQ9iJqTvljmBj+Tq0M84+SAPvs/d72DuJo+ZfkiPFb8Fj2lRsk9lRISvUki5D7cK8Y9","xzqgviKMqL6H3kQ+t/UevUoH4T6UqqU+3J6nPTiddj43uS+/r28rP6/ZbD8d+xy/bVF1PLXzRb/V0lc+SWFcvsHGpj+lmBW+JzgcvyUnNr1B37i+dQbWPR4xKz/5OxM+7OhCv7hXwz7HDYS/HUBTPF9Mjj8WM3s9At/ePXl5AD4JZ2Y9z88yPnEdtL7c2OU9a3MRPmKOxr5GrqS/I+IYv+2rk74OcTa9RUsqPrXTBT6jzBC/jDYAPh7x873p9G++yuKcv0WStj49SRM+dd0dPkgEMz5za6m9AFROvXfzaD9fgwC/oNmrPZmZyr6Pl48+0z37PTVOgj7G/U4/r46jvxbrBz6PaAu9Z1V5Pephsz2RHMI9E9SlvhbuHj1HWqq+bcS3vWaJ5L02tss+cgmJvopZOT4eEym+SHbsvTJo6b0iC1I+Zp0UPitbjT7ddyA+jAeVvp3FdLxFXBu/VujCvOEhqr2EwZs9dT8SPAsDnb5g8cE82cjfPYAJAL47ht4+GwUyvn2UXL28UQi9PuziPpGMVjzQANw96H3nvf8Yyj7+15u9xMzQPRKmFjwh9dU+R9sovofZb7tr5rA9hJs/viIllbyLjw68xhrTPL8U5LyFhFs+8wgav+FmjD3Pnu0+yM5tPkipNz67oRm+mcs3vjCRQD1G3uU9xpUlPnsxxz2s6Ca+VB72vihisb4cvHk+ycLWPb3Ilz6oELg83qXwPTUz477rmvS93P6Mvs/f0b4nwQG/accJvrkiST3jUgc9NFvjvej7kj0rTVQ9tto9vVQHW77/obQ+hGtaPhlJ0L5j3BW+tKnNvniQFz+C4KS+fEvCPSO547zkQHo93i/LPae+fD5H87W+hFOiPtUnnz5pSCC+kAIcPabOkT5zoQk/jeIZPvP4Y7zwlAQ8R56gvcTRzj5F4jK+8cVfPqdt5j1MOYw9KIGBvXsXJb9YZ1a9QVrtPLH7mj6xZ4y8aX16vidmkD4IYQW/jCObPoJEHT+vylk+DNgGPjmtFb6zQRi+","BGxGvOxy1b3aO0Q9yozKvanper1ES2M901anvULK2z4LPAK/7pecO1OrT77/dYw9S6jqvv/p0z19rMk8JgiwvbWQMz2klJ+8rIIzvhi4cDyU7oI+nOi3PGmHoz0EEXE9tbvhPB0zID64EJi+1jtyPnL/ozzoKRm+X4LNvMSWAT5ZLRi+dSSZPVyq9r7mMie/PcD2vGFQED8bB1K+h91OvYBwfr7ImiM+/516PbbwXz6XuWC9gx4NPiMWrj08ZFg+ZB+Fvsz5Hr1alo++k96GPtlIQTx2mC0//lMwvTW2mr4Jh5I8lIYgP+aqXb3nDpA9sEN9vTKosD1vmIM9uOMBvlb8w74IvPC+CUsQPceiez4gL4O8Tp/svqcbCT6xuaU+oFeuPncahb7q3Qs9M8ytvlMMtT5Apd++rou7vpu+Ub2+Azq/ODUwPh274T5+Cqm+srv7PLMYTz7CsP2+8Q42vjBHWz0aVpc+hkkyPXKoCz2TFxw+blS6vnWHMz5Gddg9TxqQP+HWGT+rr7i+jAIyv9lbbj5pal6+rriTv8PBQr6umxu9yEGWPpap0T4dDmI+NYOfPkTp9D5PTRE+UbwiP2bfcj5iPwc+Pk8mvpr9yL1Ahio9ekWPPkN8OL4qCSQ+zGAzPpifij5po3M8G7fsPAPSbD6RrYI+3+8KPhfPrjx0UgS+AbqZPfFyD77bMZS+lEqovikMor1H+Qo9CLM0vrxsY7wi8PO9oGwdvvAk6L2BAl++fzN3PQ1KNr7TBOG8eTVZvulQzr2W3VK9jzhPvmSDF7z155O+XUwtved6Ub43Xbu9J3/VvdPqDD4ZNiO+kAe9vgsHPL5uno29RnF/PT4P2b5+uqY8g+rEPa9tYr+h1Ay8xOcgvrrTvb386Aa/IZgnPlUjfL1k5kE9RaOnvujXFb8xcOm9BStRvuXBCr7elUG9gWM5vs0BHbyCY9e9qdlEvmHbSL+l8qG+Lw+DvmMYC76iRv29Is7vvM63Bb8OJ7k8ZUVovvEfqL6I5ai+","X30fPc1qZD6rxrG7qoUHPYmJYz0AlEA+fm7cvL/ht74J3Ws+dcW7Pi6KI730pgQ+Wa+ePWh3Dj5L4/O+RLQCPhd7kT1fhdU+lOJvPUDffr09xjI+YksIPnz/mT3dTwM+arq8vmOkZL11v1a+var/PbMfPT7AIRK9Oukgvs05HT3ZGzm+FcmCPmYZ0b5+GRG+RsLbPn6Ok7yNHhy8ECKSvl3+UL6rOSM8sF8Kvm5Oy7rFta48XPxov4nuzT5wYl6+/+iRvcOE47w0fpa+70k2vfrhcz4bZUy+JdyLvVI/Cz07PAS84TSDvdvTxb2SbYy+EHIKvf+LUr5XyRY+V5QCvbSh5L2yeBM8I+/yvbc1Pz1p+7++EatvPL3cJD10dg0+ghiqve8omD7aHc09KyYpveaiXD5IlMu9G0HEPV4Twr124BW+ViHsPmhrUb07XnE92ClJvuKdDL53au69aLiLPl9MAb4VwM+9srUsvRZyJr73FHm9Bxh5PLrEr74yVqY8XmBkveaVqD2WdZk+ebmPPYy5Ab4jEZE9oN5SvRCqy72c9he95x3APdR+n76dMfG9sZwIPJCy177qDdc9auPtviscXz41wtG9kTSWPedpA76VZYm826RePhqQtD2hpA++8YbJPWXECj7Wk2e+wUuBPteTAbueU4q9/y4bvTtBFLwdTaW+7WMZvnYL+Ty43Sy+NjM1vpjPYb7LLxC/JtgIvP0zvr6WW4A/fQ8+PsUayD6HTR6/alvzvoIeizpIBJ4+sNCZv04mi7wONkW9xrH7veH23j7fKQa/6XXRPmUg/708qc0+ZVVovtxti75iqrW+cveZPme+aj42/4o9zyqqvuuQNL8DfWU913MmvvVF5L6Zzua+TtXrPYLd+j4XlYG+RUxYvc+CNb4RubC9V/mtviaaD74CmVi+requvvhokT+eIiu+zcFwvo+eRD7qY4s/Gvthvv7XI74gvHO+mZLMPKtuB75INRE+zrlqPWpU3770NVO+4qilvKwb1b7QoSE+","fubYPXZcnr7KLAA+g70uu3kigLwUmuS9b/YnPiG2mz4Nedq+Sa8APopMjj6magG+t/SNPLfECr095lu+0HnNvC1MPDygE0e+iiJSvu/dPT3zVcC+rKH5OedHQr7gi8Y99d4ovfvSVb2k98u9FejKu4uu5rz8waE9etEZvbiGgb266b+8EdBzPk34o70HDcs8mUDwPfx/yj2MPte9dQDqPXEPjz1wlts9pNQRPT7SYT5Oo4I+iQYHPpBqkb2oYXW9snBVvr7vAz5KM+U82PANvRIwxD3lXty8j1E6vn7HfTx0D5e9A0qTPk2ztr7Of+08w2e5PaaEyz0kFRc9AQnHPNz5iz23V/E91eEEvu9TTT6T3DQ+WyUhPtWwF76IiY6+fr6tvkjPVT4pZ0o+wNcMv8XF3rzNqa++pDoZv4nT3T3OOLi9KM5gPj5AFbugcDc+tMgCvbyXmT7vLsA+Js/wvXI/Yz6tA6a+3eLNPguFAz/aqCo+mszQPdXYHr6eByq8eFp/Pu2PWL7u9Ve9fYESv9awJL36mwM+z+bSPh6eJT+qTwS93YGcPZK7wr2eHTu+Nt0qv0jLvr45FcO+D+drvvyHrD070lA8z+XovrMvnD4c39S+3Am4PlbIrD2uLGW+VaoAPtJ+6L1Z6gu+wWemvuRqGb6b7PI9NOkWv+aj/Tu77Xu+bM8kPq2rgr4i3zK+Y6SVvWSgmTxPpZK+xkJlPpgLID4zWUG77h5MvqY4hr31zh2/n94qPX1wsj7DF7C9imwGvjgwaDz7R9293nbivaAD2ruxTEY+EqePvftDEz7A6Zm9T+oiv98Yuj5qQb893OylvS6FvL0ZVqk9x/YivfGT8D38S829c8cSPuszAD6s5j4+exPhPN/Gl77nq4W+H1gDvg5KsT1J/Ga+xea4PeCEF7/TNrE+rRgPvqclIT01Y/Y8sIlmviZ2Fr44jyI+c86YvhOAgz7rBES+ldqiPqaKtL1Q3LU9HoCtPIjb8T4CZb28hIApvYogyDzkKp0+","MeCnvnXm8T1Hn2c+2GQOPiCAmz4fcya+laPnvRQiDz6VYbE9DfiWvoJ+gj+DXn2+75vqPW5E5b7KdrO+udeOvfsAeb92t4m+qG8DvxXd1b61hNy+LaNiPhKj3T5os1w8e/XuPs0wnz60nii/hmU9Pao+OT9GdgU+bSAHPzUaUz57wBW/88SPPhH0H7455Bq+bBjYPjarGr/BmCe/RJKuvs0Bf71H9Pm9p/cWvjPvIT6cHZm+5vkxvn5S/71iTBU//6gSv3RwwLwpMug+01rrPncQSr1J9Hq+v+y4PS8Pkj63Shy+GuPzPvbbPb80NY2+EvnsPsdvo7xhqSE/gUkOv1Hugr0ik7s+vJYaPt6zGD7VbXA+WyePPB7B0z2zt08+lOarO4E4DD4TWZo97oxsPrh+7714PvQ9LQ5kvYRYzD1zFho+s9CMPiDgCj6GXis+WUEJPvJBqT21DWw+Y32oPYr7Yj7GHtu9B8ZDPkamoT5Q6aS8M7CHPqaOqD3rq+s9j7BgPlh+iz2qu80+TiW1vi1igT7nmBC6ebnwPTuETD76qsk9HWnBPbKueD5MLR8+BvnLPX9GUT6Kd3E+Irr3PM48R777+Zw+z+fhPVIp1jxtmrk82cgDPynnRT74nw8+FedYPnKiJD3lal4+mDFqPkflBL6llbw9htCEPZJdGD6c6ka94/HMvXTMkL1nLEy92fpKvKXBKL7cyAI+rpuIvfUyuD48qEA6bLF6vffTOr6ormq+dygvPvHbUb1jVLq9au2YPk4Ncj5YhZY9d0b2PKZGrjlq8g6/p7MKPs/rgT1VeGE9Njy2vWdD7L21FI6+hgDWOTs29j1w74y8iEHJPbhKqb3qC168LyQcPU3IEb77O0s9Yvf7va0/pzt/E+C+CCv8PGn5q72fnJS9uWcOvVhqRD1fu8U8n3PPPNv21TyWup89AzE5PSLJUz4NYnu9OjsIPRHJj77k5Bg+ucYgPfkUX77c4tC9PrsuPYOgJr6kJkk+vKCcvaaAAD9a1DE+","B7ccPsAOPD7QyF++sMJEPj8WMj6MxbW9D5l8PT9qtLzLcaU9oS41vXiYzD08kv+82SW6Puohfj0SsLs9RMzNvJxglj0aLb++bZ8qvmqhqL3CQCi8arcovUVP5TxLZUO9/8uCvXE4Br1rCN890ks+PW8K1D0ETIQ7y9cFPgwmVDxka5K9ICOdvUteyD2VgeE93hDGvI/eLT1rz7w90veBPu9JMz6JVKs9L5C2Pi1IJz6ANiK+XVuWPaLqIj0RhLU8XRSRPXHxiz2/kQm+DwsZvp+lb70yJYY9ANJDPYWTZb2Ruz+93srLvWSUfD4ddjI8e0amvBfV6r38EZa+8TsVvZnpRDzb+L8+4juFPSpHZrzsJi8+EsTOvvOchT69axQ9e7CevgdJbL6/2pm+RjK5PiWM1T38p7Q8cqLnPjj2Lj+2Bhc+Vau/Pn7N5j3V4sk+61WXPsBQDb61Ktq+h4t1vVTFNz5Y7pC9+igJvrbWwD7m8uw+KU2kO9JtAT58E6C8VILcvokEST7++W69Km2BPYvBD73bJGw++N//PaPDKj8gfXU+FaFcPlB8UD6vll09FqGnPtz/SD42eqw9Zwm5vvah3D4Xo+E9a+GtPrkTEj1qSuS+AmjePsF5Xj2KNoY93cYeP7UGFT6NM60+lYzqvhyC5D3YJT+9C8MGv+9vJT+TYys+A4F2Ph2OpL1tvUq+GxZXPiY7ej3n2O89CBlnvGOZbT6gaIk8FmArPvr/KT7YxH4+PH2FvNJCmz6XrfE926L7PWG+m7zFe5S8ATSHPZalij5Lvy4+vFuFvfpeHr7XQcm8g18kPjnL+T27syY+yYWVPQ/Uhr1HAyw+5OCeusyuary7koC9sAgzvnfhEz/aiq+9cWAVPs5FeT31AYw+DfRxPWd9LL3ybeI9ks8SPumOODwGPnC9f+pnPlO68j0Biie+27taPI9Qrz1jKos+qQEGPs8oi71WKwA9I83XPWsHlT38cAE+GoWaPUwS0rz+oXo9IE+lPfI3nD2s+B69","K18xvlm2BL12j4M+vF2yvX9Ccj7xUAI784tZvit4KD+h/SQ9W4vQvYOVjz2v9gm8LNTwPhYWBr2eZuW9Tw6lvQqEKj768gI/EuDUvZNOWz7ikeq9MfnIPoNcib5prkg9f/sXPrC9Bj7mwci+bnIgPqRLMjsDp6I9tiQsvc1BMD6i4xs9gOzFPuXKfD3g5fK8gJDFPiVh4D2XmN49OYybvQniDj7X4Zg9qwQRvWsOtTs01Ik6IKgIvpgzlb2UbT29oLYkvv0qhT73hN29hlWXvflHcL553Za9wB0pvUrEbb6M/tY8Uu1GPWrQaL34gBO+Sqr1Pcfoij4DJyq+cZH1PBw4GL07x2W+NDsXPqh5NLwz2W4+36JyPa1Lhz0U+JW+1u0APqh8szprZr28xus+vXnlPj6ozzM9Hg4vPOd7vT2KA9w8RmppP4dRkrzWA4i+rpwjPvNkOr4a/CQ8GBkZPELPaj564F09Cq/bPZPO1b4TIVs9C3i8Pvn4xryHaug+EFouvsMBVT5Ip9W8rrlvPSkR4L6v9s69cQvbPZI4Gr0+BO691gE5vdPFZz0tNK27PCP4PbQkar3v7gI+5UKIPprKrLzIf5I+n+6fPbKQo7xDA/S71Vy4PXPXpj2XhwC+Q5TtvWxBV70o5yW9qvNXPXKuHL65J5W+NaiNvLJIFj1wTEG+f1xAPbCIWL2JosE70H2NPhD6WD5UwBq9cerave4m6r2oG3E/JvYZPsCTkD4hbtc97Y04vzX1EL0ek4c8LRniPeROpb3YgH0+IAFLvHfvLj8i4hK9BLktPgrlOzsTCJS+2E85vSKHhT5BlIo+YnYyP5vDED1BGA4/FPDqPdl0+T7d5om9XXW+vUdC8L0nuMY8nLmMPQhYJj8QAN+8Y+6OvQ5eAb3asV+9kW7Wvf8WFD6mPok8ti9APX5CNT/FNmE9WguGvfSSVj2IQ/Q+Cva1PpFp07qHaQS+Lp0xPiDb4D0MQ248ixgCvyMzvj2EXcK9Gy+ZvX8oNj4vKuy9","rjVjPs3I+LxTW++9EUCvPf1SPj6ruGm9H36vPssGHD6MoDO9dU8BPs7fqj4UWAU+FGwKv4zAEj/oJMG+r/uLPk1WNT18jNU94yqSPvmVej5Z+8Q9LPEmv7juVj4Cwwo+/qOXvTec2r1BcI09pH5bvjuiGD7AQAI8AhfHPT6y/73T4Aw/WUzDPVCbxT173WY/oNOQvV2k0Dwh5WS+8BqsvonkvTuE0ew7pnPPPZxD2j30xFU+rB8oPlhjWT312lm9ZO6ZPmhuJD5tp2M+UJCsPDeADj5lewc/J4gOPj1AEz6OdT0+lZnivlZBpLyebjW9h2CQvr+Wzj2wzjs9demyvltdz75t0Sq+iB75us7kaj7jiVY+VBHiPcQzbr5sN5E+pS+zPjBacb498pa9kXRFv+VSqT4K8TC+yInnPU4ner6FSoc+aCkEv59IsL44jEO+pMAEvvSCjj4h8Ie9R1Rlv/7ZWT6QurW9ii4qvntFJD9h4bc8DGBPPi0mbr7Q8ZW+DR5RvlFzEr9CwGQ+Oy0vvCigG7+BNQe+Wp9bvdOv0T2p/bU+r4OcviRkyb0JvYg+WM4BPllolbtnCgW/c0zAPi/Lqb4gUjc+CUQTPn2vg7zAbum9nepmPWydZD28+5W+NSPYvK5yTD5vBVc+XE4FPviQh71at7k+21ZiPh08CL4xSy68kMeFPmDrAj49XEM8eMnDPJxjvrztxxO+RG7AvRrImT3lcAe9mSYLvnVKrr5mGmE+FBERP3HalT5OYXM9hBB9O/aFMz2B9Ia+fBjGvTXCxj5iK9q8XViCvCQGMr+sAXo6dTd7vv40r72LTaS+tLsOvg1noLyNoZw+Tt1Gvl1NwLzB2Ti+gvkNvkKRyT4XbTo9/qyAPgsnLz2oDUs+2LYQvtQrgr7XXrW9BB0RvrrLJT3uZ9c9kfUSvU60d77UlQQ+b5q7Pr1zrDw/jqA+zH7rvqcBYD3Ci0s+tOebvknLl74Jv26+ascuPgcd9D1wKio+U0atvU/+y75hxYw9","8XmTPs3aTr6ST2e8S/+EvSWwjL7icH0/pg++PnJhnz1/B/I9uTcPPuMGz7zhTzG+MdOGPssRvj7934i+h5aRPk3qCD/RaB4+O32jvqPw0D51yzy+pdMuvWYWlL9/hPc9MvPLPkVzvT4kGyC//4iRvqNNbr8d94E+swMmP8oLYz7J9wk/HreNvvfot70fBqc+fGTKPq2tjT4A2TU/k/zQvWaqjb5mnja9R9nBPAsJBT/W7TQ+Y3e1PjVRnT5oDQS/APVEv7V4M7706sO+pmovv74kQz+5Gam+G+0VP7jBND7nOpk+phAYvecOdL70Lv4+PN7KPuWOAD4NzXA+myfTPh2Wlz3CURE+wywDvoyWST3C77Y9HvFkPbLtD77E6Ty93F5tuzqMb7sb2/68PaOFvqS+4zyKh3k8ygwUvgLH9b0AggQ+t1n3vNNBuD3EQhm7WOYPPsLfOLx/Ja8+CyWAvvAt2r0S9Kq+AyQrvbIJtb1JaTw+VE7GvbZ+3j0SlLu9ZwzYPRcLLb6gHiS9dfUZP/f+lb0SyUE9H/kwPaTK8b4DQ1W9D1IzvlqKf70oT129UNe5PYLZEj7lptO9wO/LPVGJRT64e2y9qEw0vgmqjz7uxfg9cKbmPvY8WT3eRaM+Wu5SvknF0b03Qba7KNO3PSdxRrw7N5M+mAQCPRFwrD2I4/m8eabNvQTsYT7OpeE9KkY+vp50LD12I1Q9etFTPt1DJb7fPAa/EIw3PmQrrT4+bDk+Xj4NOzNfFT8eIo+98JHGvcbIFr9Fd2I+udoJvvi+Hr6yIqE+cCcQvtt6LT6BEqU+Z2JIPzk2FL57ZB0/CWwJPvR5rT1RS5K+XK5dPgVkCL6/RoQ+qXnrO6p6pT6n9BE+Wz4PPlk1rD5j69S+T+8kPi84pL29C+S9aLElvRsMjT5be389E0JEPVZPsb3Hkxi+euEovh5HEj8g8aW+h48DvmSPFr1g0bU95AGzO4iW6r3coVU+LBKpvTfP+b7Y7Pq7n+9VPvQHV740hiQ9","M0rMvVd4yL3j/k29WGIpPS6+mD7q6mw+fSUPPqme1b62fys+1tYJv4dl3T0QZrY9zWvuPtIOoL3W9Li9xmiTPnfBeb3tUug+PoIpPQ9hxjxBcbW9cOb4vYVMnj04YRK+99uGPHbaqj5XhYe+R2fCvrzrgD090+Y9+XHaPFwQJ77/sAo9BUqTPuBOzb1yGxm9PhkyvlQR77sLP429RfUrPb/Rob0J7Cq9ookkPYZaGL4gShy9zcinPrtJMj62FPI9l8w9vZHLhr5PjTc+rLmNvY5OCT6ooRO9SnzAvGIJaT0sd7u+eIemvqAHCb39TIC9vyVyvlwEjbzR6e89tc5TvbDh5D7jm4g+vCrPvpafyD32G+w+IYuuPmAiwD6qB+e+vuWXvkcCTr2Rkxk/ahw3vUkoZT5bBVU/q58Zv1OHYT17U10/1tAsvtA3Oj5zfSK+tzsSP7gUPz7gB9w+ke3ZvEPXjT60EI28MbMkPhODBj/5IYC+Ka/2vU2Dhz7xwyS+67ozv5QsNT0y3qA+LmSjPZjJmD1oui2+lRl6Pgaoor5de30+drNOPiR0Gr4xEB6+swg+P1AGrr0gBF++/FiuvmFIGD4zZJ2+B94dvTowgz8GwMW9b87HPIK+dT5uK949YsjLPLyHYb5csoC+TTQPPpfG2T4YqdK9RzweP0McND71E7E9rYGjvkosm779m6e/dVMcPi1RDDwAfmq7Bk+Rv9+2Cz69aZw+t8HQPr3rBzwj/7e+HpvvPvtrv721Iay9JfC0vSFpUj4rT8q+ppSBPYkhZLs5UIm+vLFdPaECUz0Efw09Y3RHvR4Wlr51pG69CyU+vmmiq762Gdo80L78PjKjob0KtDi/dUDXvdrPyj1lL3+9pKvvvtw6RD66VAM/wBrmPc5MPz5CyHC9lfdCvi+nuDwPNEE9uR7fvfqF1r2ipSG9P7jmvTMJiz5AnTG+NrBTvjrgC77nXdg925QvPVHTmL5WEWW+jiG7PSHsqj6hJlc+6+S8vjwzRL6lZGg+","USV+vM4snT6bnpA+HauyvqLad76JYfo+xIqMvs0Brj1kmFW+Fv71PhLvjT4IjA0+oWQivjiWLz4KcDk+O8cfv1ujVz4tFFm+AoP0Pk0YMT3BzYm+15h8vaWFIT4bGgY+yj0lvmvqAT7gap6+bofKvXFbkL59CNi+d4SpvqCPazlCMqW+ekuXvvAxFT6buIY+MaWsvagaQD6OPnq+jiWavdsrsb1OdRi/gwSOPczkRz3aMY6+sXydPWjK7z7aocI9/QjJPYaXPr6hqV+9WH45v/qqhT2W5z8+8ue1vpowiL5DRtE9a5JxPihekL3++/M9Enx/vc5eAb2Xp6u+NIs/PSNVQj4Ls/M9LzZBvnEgTL2GkBS+gPWBvpscRb587fo9l0IcvxiDhT64FY28XZt6vQ++S77nDB8+FvxTPUtUGj6czz29Z8I/PpyegL13R56+ARDFvlmD0z5qziQ+WfdNvgmpvjyNbUk+ujeRvrHPo73bz2y9mqVovvMxdz2/458+f9xRPrC7Hz4SR9I9PpkAvqZQkT2GxAE/NnyEvoA+0T365nE+Wh7wPJhhkz3p+Zc+AaiBPSF0lr4elZU+Ld0LPlJEljrAZ808f1f9Pi1WOD1zTNU9sYjNvSSYSD37boC9G7uQvIS5rT6+jFc+ebhBPjOcb77XTRG+S3UoPbJZrj6YOKi+D6ajPiTQAj/hAwC/IbaCvuDAUL1yTKk+PPv3vo9Lzr1k7LQ8HeKNvsA2477T/De+Z4YsP0v7mL6SfH0+UcxGv2TsI74R4VQ/V2CtPgnUNL+hN0m/6wrGPn+B+71/Fpu9AeU2PySJUL4jFHY+NZcWPt6oQj4jWi0/IgIBvygb9z3tBwu9OLDsvffAZr4KMOu+liWBPn4sBr+DOTW+oruSPoUVAr82EcA9yCGpva+o1z5D44w8taASvhTkUz9Mo8o9l+6ivugcVT5/C7A+VBCwPk/fGr2E/dG+1BWRP5EzhD2z9TW9pm8DPdhj3740GfK9NsXLPgtaPT4zzRi+","sKwmPhpfiD0mPYA+6vgsP/tFHz9e4Qm+G/Wivb8hQD0uFz88333EPZJpcD51i4Q+CrIdPwZsADvlKMM+qMZMvkMbFz1of0g9AcCqPrwx6jyV1r69/TrQPqAbu70f1oo+n/CPPU2j/T22Wk8931ozvq0QWT7OtLU9R8DQvQsVuT7n90g/CdiBPBcHVL4RFQe/j40hPv27MT8x1yi/M9r3vSV0Lj8jLge+ycMgPv87UT4w5MA7BXimPh+uY7x87A6+XkGBva3UgT31Jz094Bc0PVPciT69a+68HTvrPspceT52hwm/Nyy6PbIfwb5wKWM/MjZDPA840z3W83w+NesFP4NSQb5clN69Y7VtvcAj/T2YLLS+K4yovtzQBj6oOGw9HGRnPtYFpj14QK+9xN2bviVd3b1HRWS+0ZnPPjz/dbztm3C+MCc8vVd5KL5JmvC9Ri2+PnI2lL0G+SY+BH3Hvn55IL6xA528wOFLPiftOL1dqss+OJqjvfSSab4gUQm+jcNXPsTRZr79uZg+YfsGPhsEor2/m9A9EUWoPn2toj0f74s9YXhzPja8+z66RLo+8DkeP5iAP74ZHVO+y1MiPm+Vbj6nJVk+j0vKvfu4Br65d8K9yV3tvR6YKL4QH8Y8dYtXPs9nvjy79wI/FlFxPcr/pr6su2C+hkA/PGZ6+DzDS4y9KBI6PmA3oz5hKpG9Xh0WvVe08L0UESg+axa3vJCirb1ysr4+9Je3vTKxBLzWaTA9eJCNOxZgBD5sJ3w9xVczPanKzj6X77m7bUzqPQTjx71Zd62+y/krvlr8Gz5TQC09P0/EO4XsO77Xc7i9OzEMvtzO073ZGLs+jcj3vYABNL16MMy9jatOPmMlZb3DFS28u4HvPoLz3T6WKlA+RZbMvt51tzyAbs8+njewPuqtmz5OwSK9HE1IPfZhhj1jEda9f7rWvV4KhDx2CYs9cU2APQ4cqr4n8JY+Xbp7PGGlOD4JBQk6FWzWPrZAYr/baM0+6xRVPg37tD21JxQ8","Mih9vF3Cnr6zi6q+R4tyPw6EQb/r7cS+k42ePbsIQD/OHAc/2bdmPk5g5b4anD09WuU1P5HKj7x2ItE+V/iUva/WIT9DQ0W+wGLUvnCbxr7NRKg+YLGFPmjRTz0JsGk/emQuv2br6j4vC5u8WeN1O8EmKb1mPJy+Cd7wvil7J77K/Zo99Va9vhH5SL53daM6hSDHvl2X0j1+31q+SFfEve8ffL0o8789CN5xvo5vdj56vLk9pDCTPXXjBD8ihSu+8vPiPo7/aL3iwGo+Lwdxvmmptb6nrDC+FarcvD/nCj8xrVo+irAOvm4j3L5llcO+ARqNPUmXwL5nW5C+xbUuPkB/Iz4h/Qa+uFjHPeTsuj16PK+9th0IvoTx5bz+Q568YsR2PlNOHjsy9Em9jqIAvm9cBb4FLgI8OppfPKk5Eb2SnEQ9E4rVvWfrhjxogWm+Y7MpvvprGrpv3Pm9b2aHPU3Vu7xlaYg+wkKZvHBwj76CBIu9KZ48vPkbKjx3r8S8HLoHvirDLD5zahC+/+7mvrYbvjrIOxy9L/ypvXWEsz1PyVc937EDPhQIPL60sCQ9gEDjPWUPFT26GA6+d6AbvT9Wzz4in7g9WFKSPXJ+BD3mFx2+M/ksv4LZr72m3f29vhYQvg2DiD2RxDm+1+s8vlvnPT0bpxI+RZZ2PgmNUj2DXk+7hK/SvfrmUr7XHZm7VfnIPcLSuj2zxAo+OWGjvqdD/zyFdXE+Ye5Ivm6Xn74Aem29ug4kvh1Dq74RbBE9veoNvraHsz4r8u48Trd/Phbq5D0evE4+9CV1PuvsKD3OkF6+gkQ7v5ECCD+uLaO+gYnsPZ9QFz74UZ48scR/vrI7RD4ZyZ++7DqWvTeCBb/wPMu+kq/0PVBLgT6+lRc/wfEgvrCuPD3LZP68kNGiPf+wfLzmus49Z8DiPD7e+7yC8bs9XU29PdWWgb++HxM+YwKCPpD4LT6vD+I9FgXYPZ90RzyDe1W+QBmOPR0j4j4FYxE+fG0Eviclor1b8wE+","d4CkPaQMQb0cnpW+SWSMvSJzzL4n9389AYM8vr88xT4J8Mw96kKDPNkA2Dvy4WK83gy4vRT78LvPZes9MX/Au2Rsrry5SSa//RDSvN2gHj3XPgU9ITxiPrutUT0MYAW9Mbhhvk+Jxr5JxpY8//3iPsZD6bzIQ6O9/JefvYp44L5PnxY+webLvkleAD2i9D4+SiWFPoIVbjz1c+29mumUvs/O+Lv3JnY+SLUlOzYvoz4+9MG8YsaCvjUuTb77M+q9ooVsPswuID0aq5++cwBuvKj/3b2bUCK+HSGevcx4TbyKfTo+G9suPWnRST1JMSU9ySnsPRUGKz4SsCk9dMFnPTCixT0q5BA+NIRaPhjEfT7GrLa+7vGIPejPQb7b0wU+vIP5PopxEb5Qy44+gSY0v/2UrbziFUi/+QegvfzK8r6+j4e/CsIQvFjxor7Wq/U8Nm0dvhFCOj3Nk8O+ohKRPifq2D01ixg+gz0TvwH+Ar8mMQG/TfIIvpGulr5YRHQ9/1ufPqbtgj5uZI2+CNRnvSqKGz4m/2O+RPQ6vmtBqbr6Z0S+bMc4Piz2u75/n1e9FvTivh+W/b2BdJm9r1pcP+K96b6hzlW9stAfPrFA0j1NbQA+T8wAvkIXhzupiM49lZ/ovoQdqzwUIqS+JAj7veSyCz4SHni9GyhXPvr+GT6p/RI9+knKPnM8Sb4XJxC9RcK/vmj5Lb7VKJK+IPydvShaBT4coXG+VKQ3vrJKE74nEZG+iAcEvhz/Q72mRli+DEINPN9XGr67Vbs90AaJvk9SRTtZJYm9S0p1uhJKOT7YndA8TzLTvfzZXr7KrhO+6n8pvgunpr03ogy9ctdRvu1XJT/iwKo88uM4Pp2AB70/xjC8DKO0vsG2Yr0zr5c+lif2PEoCh71r0kS9gc4lvsjEfb1Rovq8fMigPQmRzz0iJkw8/59EPtEQEL7L0n+9JnInPQD7sL6L5NA9U4F1vr4NWD4i8QE8iG+7PjDCqz6yv4a+qY2zvfMHs74EoWw+","xrB1PxR4170sFmS9uJC0vhY2tD6FHeE9Ed8vPgm1ib1fInY+N+szPjCuEj6xTU8+KUiivSHN/j4IOfE+oYgkPjRyH7/nBbI9oeQLPisdOT7Jb/q9S/Z+vqe0rz6tLd8+1T0Kvo4e5z0Gz+o88+gIv9gtE7tYHj2+bWgevpMnpz2rHTS/NL4cPk0q/z2jYta6mo2kPa1Glb2cY5C+gr6GvmFfUL7gJMs9mYXXvo80mb5Z3fQ9nF/fPlFSzD2Yg4Q+cOBZPo0j3L6DzzM+OA+pvsRnpD5Q6ky+2GxYviCf7zwGjaK9cx57PjGEPb7T21y+tenwvHqoQL2TqjO++p+GPc6qrb2RyZQ+tvz5PrxYFT5uzuo9EV0CPZnIarwyhBK+1NEsPqtHer6tkec8ozaavu4OUj59BxO+yKngvoOazb14FXC+vM65PtRoUj0n6Lw+cGcYvszKgr0X1rM+jcO1vuCByL37yQI+pORDvZj/vb2l4Ju+tZ20veCzi7y+9wy814XZva1jTD1Hc3q+36+Cvu3XPb0Kqbk9YJjWPYFSVT32OpA+YhThPUT5kr4ST6a++TC5PgPfdD64fV0+IEIgPmBd3r3JMgO+CsEVPnTY274t/0Q+MfDaPnk8BD6U7ry+dkGBvmWiB74dcmg+oAaLviFOur2jQpW8ZoQXPzuDCb+rU8k8sjoQPB9rzT5qDpi+QpL7ve23xb5J0I6+RtEtvqv3lL7PCWg94tdfv0Snkj4DSZi+/2gXP7mSCT1wUxI+I5gOPrqUFz6TztQ+ngZlPgyEjb0VKBi+mjC1PVr/dr5ykZM+jO3LvueoPj8qhKi+z9/CPb7qVD6f8Rq/HZ+Rvk6Z9j5inBi/4BX3Pb7da75m+T6+JRNcPdMF5z6/phC/LaNQPvz+kr1C9nk+yt00vssy3j6ddKQ+lhPovVUeYL65nNA+TZiMPiMuqr5v5MG9EJ4Uv3N3fD5KQEe+KSHqvsBPbL3NaBU/a51VP6fMgb6apE49xdiTPdw8Lb/XKAI7","yAFWPhgj4r4mMVW9pk4uveOi1ryi7w08lss1vpuKR7/M9Aw+Lf8BvPH+jTwAlWi9pqckvjFTVr4GL8i+J1UWPi0Crj19w4A92kK0vIClOb7hXDw9VX4av27+e764StC7hpLpPbItAr7HSte+xz2VvtyLML1KU/08Ci6ovbvgk7warMu8n0suuz843rxI1VG+2n+6vW+iOD5IK9m+Hkpbv7IKa77XNa++rxuEvm7OMb7LdTi9GLROvWF3ZD5gafe+FNv4PY4Brz0I02C9atM2vUfnrT0E6o4+t+TEvK54Pj4bhzg9swCAvu5Fnj0d+Bk+ds2rvvayDT544/69TuKEPSEijj4+/LC+Z7n0vt6DobzHXLy+puREvne7Vr2TYga/NXenvarmgbzt1ZU9vOApPs54jr6S3Ie9rZdyvnDgdb0EWcU9E6Epvn4CAT5ezF6+GnOSvRZufL5PkQ0+7EWFvhiB/b3oVsc9q0rsPlDFQ74vamQ8azWWvd9bz703rxw+BZt5PfFyG74vSFW9V13nPevIxb5hKh4+68MAPtHgwb7HPEY+0HeIvs9YGj0pexI7kNt3Pv4T776JyN49egHyvLbm2DwKrB++TdA2PtkfDTyxqhs+rWG5PlW5Hb4mr549PVF2vuxKKb12W4U86GIvvEs3AD1t9aC96aztPXmYtj1uETY91mD6PJSDPz0vdTs9DEA/viPlHb2HOr69eJTpPPueuT0ACba9xH9BvidsMT7yuBS+YMTZvJKKR76gW86+OyIKvgsqob4UFy0+o5oGPnz2trotiIg+NvnBvanJfr5Hrhy+tlycvdoloL4FmII+o8GXvXubWr6/9Zw9p+dMOnUNDz49osK9s7sRvtfvAD30H2k+co1cPk1VnL4+CEa9d7xdvcqJxT6ub+S9x+q+PED6Gj5Yv0a+/MgePXZNXj60SkC+ymp5u5jo2b1OEx++2cjVus9XLDyU5s69+QWPvYBDOr6J4EU+mE+5PbQIPb5loCa+Hi7OPiMkuD3RIGW+","bN2uPlcTdr4EQHe+DxSVvsUPA7/9rVW+oVAwP52uGr7KUq8+OrS6vhXd8r4D9xS8d153Pa/dtT6y8MO6lTLRvS8pib7dEMS9/9/7PuZSqTwZ+44+1O0VvkJPmb5/Bni+Hm5KP6JF9D2cVyu/hgQQvqLdhb6h6d0+zxEZvyXNx70yn4E+qgayvsz9LT7tqn0+gzzVPBV5f73QHBi/Vv4Zvo+aFj6O0vy6YefGPiF8ij7Gq14+rLHYPfzUyj1qXrS8rMhuvgiDtD0ZMi663TMFP9lpmL7S1Rs+KGQ6PmdXsL5FJmk9QRtwvJ49Qz7iJJW+0faqvGmu+72r4uS+jNC8PhbAVT443q69ORImPiKkXb8WJKM+1PqhPgGo8DyCmCc+vrg7vhiAYT4wJmC+ZQ+9PnARyb7Jvzg9kAfivgAhLL5gpts9Uk1qPsATSD4Bi4g+VXdkvcVtxz5UEY89VMKBPjSx1D11xwK+xuN4Pj5gZD71ZQI+exDKPYF2Tj7P/m4+qMHlPpu+ZT5k2s093hSCPnj2ZD7mnKc+CZGDPSTDOT/Cd5g9qCa2Pnn3xD5K9rq9MMGXPY1R3Dy1/me+2E58Pv3xKr6uJ789W+ahPUPv/T7nMaO88Zu9PmIdEz+tSLQ985/2PjH3ID9l5xM/+rCKvmRAQL6GJ08+tUk4PphTAD3kifc+FFS8viyjijzjxhO/KQXxvkSOLD7B2Ng+zC2vvsUziT4JRxU/v+cQvqGKCb6+vVa+Lzi1PWMRlr0CHnq9kMYDvhJHZ73vcIc+VS81PrXmGT8lRKW+Y4nVPgmHez2/g7Y+nskOvq0Djj61LbC+iNISvn8dBz1ElLg9ybLjvQqBBj2DYKi9HZGUvgr7uT3iAxa+uPV9vVnhyry7dKE+diK3PtmGMD6WVU295l16vov7PL5x5KE9iiPIvaJlTD1w6K6+VmBBPXWelb4OXI2+3ebSPu50tj16TgC/GHU6Ps5nGb4fpY8+ke/EvVwHgL4hHxs+4r1bPkVqa707YQA+","90P3vZkIxL7A1sw8La9dPZzyXD0UEaM9ZD2UPobfmj3G/qC+OvSUvogewjxi5AI71EUPPs4c6rvDzwA9Yi7uPAMLVr2u7wc+ZU4avjkXd72GI6G+s+pgvkH0wr0JfO+9MyMbvR3QjD3+xdU+naBTPo19cr38KLC8ypYhvWV/Pb1nCum9cuPUvvFAzD6a9y+9BOgQPmhoIz7luWs+DrMpPkOo1b2QkQ2+Rc16vi0SqL53rNG+NJWEPpJQHj6m4wu+bQUyPkl7cj1KVTc+1DZMvvVKWT2pZIM+ULYlPvVuTz4r3Ou+qJubviveOj5Odom+2/jDPfnXgj23eSA+Q4Fnvq95vr278du9wQNfPT0rKb5XAAs/5XaGPlCe2r73fjI/hvKrvnZeVj56jqO9Ne+APXcjt77f1Me+bwTTveaPcT39JbM+l7V/vdPZE77fnqS+/4yKvsexlz9hb9E9eEqKvq1PtT1n+C68gRZRvvAPIr1hchA/W+0zOQ6vqD4XOWi+fmQZvrlkOj+PDG++9FK8vprSs75MrY8+gpFPvndXTr4wKQQ/M/52vp6RpL43h209s2vrvjUZ6D7vojO+cQxMPwrboD5sYAQ9FTi1PVCvAz+EMcq9vAjCPkj0lT4fIns/8ckrPlwlu7u5WDS+9W6OPYQMIz8zvgi+gGfUvYAOu76dCR8+kn6vPKX6PD7XGBi+0hQjviZZsL23I+w+SIjBPqD6DL68kXC+YZmVvXvFkD5qMyW93nCvvgbsPj+yPoK9x9KWPqvCuL7boOo6FhCHvVGjYr5iBwu9Q3hPvnOH8L36ED8801PrPoz8N76cuVM9uhCMPsjgl73ZGlS9CUhsPtlQOb6yXZQ96artPYy1tTsXFOk9QpEVvoGPpDwKqZc/xbizvk4KnT67wrQ9Pj9uPvUqFT4cWqe9/9hKPlWPqD5Evw+/aonLPYgnCj7WKL090DQkPvyjMj7dB1Y+G4XWPvzSdD7IJiU/6j4KvdZKurua7cK9ZkgDP6KPgz07abC9","Te05PQ5f17x33Cg+pHP6vXn0IL2TcJQ+7Xn7vqYwP72dA368VcisPjONmDwXq5O+QhjPvoPaL76Y68K9G4nePTD9x7y1l/I+3+HhvmRcvT6U3BE/b3z+vXZTirx5oc4+fBgRvVCXcb73wwo/azuuvjLvGT/Ua1i+xpmtPYnOmL4dbVE/5F7fPFHOiT7/6R++mzsAPrmQmb1Sxh8/w/sZPgqgOj2ZIr0+DPQOvnfcF74ALbu+WeOyPBOtNr8EoWG9MU2EPVThCz6HN7y+Q16lPm6KD7/5d4m+3QqBPdGohb8NTcA+HpTzvczNTr3o1529HP4UPgCqqD3x8NK9vjmou74yiTxXGZq9iuyWPvjwKj4KKQK+x1+DPlm28r6iLRE+pAfmPpCZgr2zo8M+i907vTLOHr/Aeqi9dcBQPo9GAD403pg6Tgp7vti2Qr5JKZe9mX5NPd+sBD6kH1G9H5yXPi7uPz0Gk7S+kW8MP5KnLT7x7988T+cDvrD1nj3c9im+qUqAvfPwir2kJQg9TxLQPUFTjD2dO5e9CrGmvrw1nz0E3HC+kgAXvc3loz6LQNo9NPR1vWFirz4zYJW8vK/mPJxiG70YAqI+6ecsvi5+xTwx+4K+0YyxPqU7ij4bQ1M+1IqZPemRTT5NBim+Rl8tvUe4+b0Tjpk9MROFPYsH4D1QkSG+JISNPtuTjL5eOFu9T6LpPSIKJb3ee0E9vsUIPxL9SL5jveU93CMdv4i9cb2/P44+GiYqv5RrKj9k2Ac9bnR2P4mpjL4LjJM+ZP68vonJgL2x/Eg+42WQvQzQt7uf2iq/PxeNvUG/+z6lEoY+u8kpPp3aIj3wNti+HgUHPw3p7j7algO+JVFxvq2sqj5cHyA/yhcUPr6PZ7/yxog+/F/CPU1i2T3GVTu8UbpaPj9+xbxQnOk9Ph1TPXz5nr8J3jo/vCQlP6rTw77PKVW/o7vOvmyykz16iB++yEk0Pk6sbb1itNI+lkxuPoc7Sz4wZMw+1jMWvusuMj72Fjy/","KRN0vI1tcDy55pc+BoLbPEJZTb5Gn3e+/bzUvRFCwb0ZniM63ciOvNHbIL4YaII9ckinPo37zrsZbSc9dUmIPbyxF77LMTq9z7/EPIfkeL3V9bI9lgE7vlzNKr1znby9qJ9KvlBRlL6MSSq+oRiNvvexRb1k2LO8L2UnvmWKCr44NhI9i5q2PUXkDr1IqYO/nnX3vc2+iL6eumS+NKjNPjAUEj5Qm309lcxvvp3OB71WxMy8C/ZmPFgymr0hIdW+jGyLPiX9Jj0s7he+nTrqvWJnGr6GZFy/kIySvYJ+lD3gOCW9yiIdvF5lOLrqnVO+kYRjvotWzb2Oz2m8DvCOO3uUar2hGJ8+TH09v845PT5769i8ACCEvdDNvD3bnxq+E2QiviYctDun0XU8Lls8PXkWqz32LA8+4nWBPhRRKT7QGiW/cvK4vkypjr15los+zzeQvUj4qz64Bik+mg/hPBHaqr0djqG9oxiJPvSiuL7S4oG9CIpmu3pap726lgW+CoQfva8G1j2shIm99Dp8vLqXVD3oKqm9wceEPT//mj6QhDK/CAEYPrdW5T1UsDU9RTu1uwHCWT2LKLW8ePRjvd3eCj7NPqM9/KkcvrACjz2eBma8aSLjvv++Hj/INAq/GiGFPBCqDL4uzA4+H2ShPZjAVT38k6+9aWbxPU5Lz73JLKe+OygZvh0hVL4y7Yc8SOgXPdBEtD3GEgG8vsyEPblBXT5UlPm8jQjhPGR8AT2QlKm+kIUNPWbhoj306+A9ptk8vrCShj5KfrE+pNgcviGCpb03nFE8lbmPvbB2BT6Qf869t7XivK+8Qz6pEHg+gGKFPcuLET5bQ+U7FalwvoEEoD78Jfi855BKPRQfs715kFC8DkCUvdfJVL6W4zS8mkWAvTuANj6WD4o9o3msPhtwerxrZzE9bkmXPUwdIj2ndWu9h0svvgmoc76vrxi+iCTnvX0djj62I+G8684oPcATNr4g+54+rGhJPWrQsz26yzc+XKCqvaQtSj3l1lU+","eR2tPHDGa74y98i8/fcDvngTRb8Kgia/IP2APVaKZ71XmIM+r+37vcTa/L68/OC+fCzJvdrkyD1a67C8Hz/ivVNOhD0OFyE+YFLcvgYnqLzzf14+hOwovG/SkD2tTrE+r94DP+R56zxHpr69ubjAOvqq3z3zrOa7JWKkPN2+EjzAGMy+x4mDvrNZtD0sdaA9Lh/zPYHi9r6P0g+/o3ZEv2qhKb4GT7s8VkcLvrlLxz0OmlI+JoE1vLt3Fz03Mwa+3aW0vnIRUr3wgNW9ZPA2PxN6Mbwn3o++CTPdvILitL4AGOC+0r8vvpuUmL7kuwO/ZsVUvsoAJr0pjpS+2Hssvy9Ngr0EiQ2+ArsrvtBRDD4HqhC+NZI0vstRB740QyC+UHeqvjWBUL6H046+H0EEPeTtBL5X38M8hdKuvvLAvb26SO49xL7rvvg4Aj69TYO9kEA5vkd1Az3hbYa+hnz3vZjggL7u8+a9lD9kvu2rAL5U0/O9uFLsPdTPib0GDSS9pTnUvRRwYL01WQK+J8S4vjFYbL46IXy7G8hvvmNCrr1d7gA+u9eoPWXOx73wu549H2umPYmoRL5eUNi9/qLfvV72vjoM+K89DPEHvko/nD2cGBQ9Hk6ZvTJ7ir7OgJK9Ea4QvstxsL3l2SA+TQ2tvXZ9xr4vdOC95MU5vgTz8bsze+09yuncPT+H773yfZM8hBiQPnLP5jzG9DW9zpAvvSUs4j0ZiJQ+nds7vf6aUb2DiJG9JpLLvZmaMT2yF3u+Dgwqv6IFoj2RIJM9TxWGPBLxoD0aC5g+dTK5PeLci70o/5K9O4CSvHzzJr2QG5U+I3FtPpaGp76b3TK9iGiXvWC2EL5Heco8pOi3vi+LAb49e8Q9RBy5vShr67x++9U+aYUCvb6bBT6S5rW9XlsdvWtCkj3mp6Y9ahElvrG8+70Q3oe94xsEv/ZAqr1Qdee9Xb65vbLR4D5Xc4G+caAsvd1dqD56jMk89eplvWCboD5lR1y+b1zOvXF3Srydhiu9","YPISvVbkXT0vLsw+h0uaPT87q72/6YQ9dhKBOlrZrD1e3uA90K7JPQNpWj0oGdA7KeIePUyHEj5O4J07sgwzPHAVjr5OXJQ+aMudPFkWnT64E9y9r9p5O55uor1YncY9j2dPvskwEzx58wS90eLIPSxlIr5PKRS+2vSIvRNtpjyhsIC9XHFqPkOEWb6tS3g9sphgPGTNib6mUpG8rLhZvpaxxLw76V89qViHvnMdYb6jLxU9QPIGP0Tk9D2y10m+UPaPPBvyl76tgse9cXDGPRFJa73mGwU7SzGWvrXZoTsLScI5QSKJPL1W8DwTwYe7YbG5PdFXerzmRq8+f6MTO9iI+z1xS6C9P2MUviC0yD7CPBW+raPHvlj8Bb9aZca8tu6nPstaQr9TWBe/uhsXPjQ/j76L4QI/M4nAvnn6eLxZHAI+zS0QvqW8Kj9aawK/at0xvttHWz4jT5k+P14Sv5iwCr4Srn++XRgDP64xiz0Tv9S+N8dBPtl+jL7r6IK++WbhvmNDcL5RQbu9+RUyv8Q+O76myDC/kcb1PkP2Ir7/OEi/GAMfvpHwbr47I3e+3rsKP0ToM75QrWS+vRTovgtbLD8eAWo+NECmvhr6xr01Dyu/Wsurvak+0LxL/Qq/ZBCGvvi1Rr6nFB4/R3dIv11rQb68Aam+1NrsvqtrJz7NHUe+yxObvnAAwr1htua8S3zLPDoBHb497pI90SoZvpkPOj7uXAi9YxsrPtFHRT4QBcC+TpsAP9MYl749BtI+LFETv120o72crp++BUgovjWL17xCKiK+fHiRviuuuryfvoi9U/E5vjhTiL4Cq5u++hK0vq2+gjxo2yk+sbu6vdJQiD2Mfsq9QybXvszeJr6YwXG8b+4pPYmDbL6O3Wm+2oUXPXYrDj5Kyyi+mVlYvo3yj73FrDq90CU7v+7eM70UQy4+sKrYvcVWCb8ds1C9xuMqPpz1LL+AK9K+8+BvvRaFnL4I1w08ufT+u9RpVT7kfzI+6QSvvns2cz4mmNa+","nt9MPxEFhz37lHS+efTGOzgDMD7X/CI9Dw8BveshCr5Y/Jk77AIXPQPuiT37GM4+qX8lvjNRub5Vi6Q+hEcavoILsb07eta9REJKPh2mpD6uNwM+Gf6pvT1dmj2Yrf89FYkMvuY08j0JqqW+iLH8veHCrL5OBE4+ptRRvSsgV71WQBW/mJYevoyXJD2rnYY96IzlvLFipr2t7va+a+w9PiTyaL7tR5k9s+eyvT40JL7HEZ4836rnvokrgL4zttG9hVh1PmD7hr4OX5Y+7U2EPdfcUbzsEUi+bceVvj50Gb/rrKG94VaQPJCGxDwkk8M7tuKIvk4XBz0acX6++OyqvlbDnr1/V1u+v3l6vhmmeb1VGVa+NGp8vdITAr6a7zu+DDHYvDeQST7ZnX49qCOqPslvnL773Pa9ikBkPeZXF72OPre9OUvvvs0vnTzJzAa+kAnxvi35Rr497/I+01qLvYAqu70CKkE8d9SJPsmaSD6fWh++4BkfPZohez5noiS+Pbglvpbg/L06ZrM8KvrWvu4QOr02eba9l5x1PnnqtL68Z549ridSvdD5b75q85g90irgPauEdr79saM9MY/HPonbkL1Mzrq9o1+DPlvIUzzjDS8++VtavgvqKb7PScS9wsZzu9KKrTqx+he+/x+YPnSNkDyICEa9W0BxvlXj5D21zRW9WFy7PezeYb2e6dS94DS9vSLFUj4sXXq+FX6gveiZpT5ncsi+m2ChPiC4k76SEtc8dOzwPVH9Cb+4xN++rCb5vMSJkL37/iG+x4+iPSRCjj3Q8yu/lYqzvTGfHL2qOFG+RE66PegOh71E7qG9OoiHPl8mwbwL2ga/H9pePSMw9r4edg891WrfPYa5Tz2dleG9yxJjPHybHb+STMO9fhILPsQ3TL4RIKe+FsLNPS766z60fOA+bq6APaaLbj/PM+M9Q4Kcvn6RmT0zrZS+sid2PtW8kb5bnye/oqJFvt/ToL6T8/e9hu7UvYfE8L6Piiu/vLKLvYlBpz4u4Bm+","Og45PsyVub5dKfa9mLIPvhrWUL4r3EY+iAaTvaQlAr9/vn49m0eXvigwMr2q+4q9KEkzPhiyerzwMaG+dHflvExo/L2leXm+1ZDePWeULL4CFUe9g1rgvbBM2r0vlHW+iDbMvVEbvT2b/a69i32AvsGX5Dw7koW89feIvvz+kTrxAb2+SdevvINSdb7opEs+UN+mO7H5ab7ZUFu+ihQAvTaw9Dp4Y6+9W7HAPW/ViTt8dZS9VyuivtEFLz1RZsm9scmXvQPHEb6A07M7j2oevlXFyb3Jm0K/QIeKvjfiqr0d0V+9zQM+PQk6Jr78dIM+10gxvsje5zwp0Ja+FNHAO3y/jz1x2L49BQzMvh1x+z2BsQe9Ag0CvuSXK77FjB2+wE9uPYyOtTzYU0M9oZBXvvYBhjz63Ye+pkFhvnlmAz6fz3S+CWGtPTxb9z1SRhA+RbXvPebY0D6dMn0+IDK5va51Bz4Q6Ki+5aHIPqzwlj6O4jA+VeYLPx76+Lv7gOm9wpIgPfyZLb5iyaw+4uTFvS3dHzztdhW9HoVWPkCPzD4zg6q+V9orPr3z9r02Lvw9XbwOvviejjzKen6+eqn9vhD3zj0KapG9pirlvoBulrstmQW9KTwfP4Cw1b3P8fM7C/cgPjQggTzltSK+9XS7PoaIODx6JLs9h6IJv/zeHb5gqCU9ip+juy7gTT748Pc9OjiDvo2gBb0EZVI93wMGPq4l97084IU9kVy3PQhStLtxyni+Fuk2vryQgruM0je9XQcavoO8c70XViQ9FUyGPb0zH70fqx0+fzOtvfsQoD2Rvj89EA8OvkhbiL3PPJQ+Kc1FvRDN0TxdfTS+bYwEvZDqxT6Mzzc+beNyPT4svb0sl7s9SWLhuAa4s71SNZq+gUqFvc4Rfr1Da6U9vVnCOirngT2Nc8G9F0+AO8wRaj7HzQ09qVSHPtwG5j3QImI9V6ePvaeJgLzYVYc9dLCsvAJAFb3y6Xk9P7+NvZM0aT7t9iE9EhAzPfjpQz+PqJ4+","ygoGvhwVcb50Rl8+OhoIvkmF3b7XXJm+p/umvQ4pwD0I7nk+6/mAvfuQnj64MrE914H5PTxuOr/0l/u+WK77vt1gXb/rUMe+4lqrvjKeor51hUy+1bmBvlSaaD7ANbE8iB7eviWSZj5kSxm/D1h3vj6air5BM8y9Z7DLPSmnzT2zSZQ+PDHZvdEtmb0Xw+G9UU0TPdojX79KUoi/Hkx4v0YiRr2ClNG+JibNvubVjLwRbs++OirbvvjkST0wGgI/gghePmZnUz3nJSS/kWYRvvN29j6VB26+dhrivWO8XD6/JYS+Afl+PFkvDb8Q2CQ+CJTVPfF51jt9e20+f6Y0vg=="],"bias":["aBtDPfLvtT2WH889Gt8rPbYmIj5M3+q8QE/kPZJBWD0rLPk98y3ePQF+MD4qG1E+8485vLu8szy+AZQ+JiQUPffh9j3K+wA++TXSPRNjVz7jUAA+NWoNPmsc2T10PBU+YMcoPpBieT3i6nO8pl0XPu8KLz058mM9GHm4PV5TLD6ZBKY9K6/BPT7SFz4eyLy8FVsHPvGsCz4e6kE9T+DrPT9RlD0KevQ9cVoZPrUoCT4p0LY9AZxJPobX8DyDW4I9MxcgvaefFD6jaxM+kVLsu20b1z1l4Gc+1tuwPSNPnz0OUrw97CmzPdysKDxyTZM9YhUbPuZLTD5o7bc9Q+MiPuGFhD+KlXM/xN2BPzGEcz8053Y/NmCAP6JMhD9Oen4/RUN3P7KjgD+xxXk/UQtpP8qIdz8wEHY/OmJ/P/cVgT/dFXo/rMZtP8gjfj8TP4U/7UN1P5SecD/BH4E/dx16P9cegj+dm30/DlWPPzkzcz+k6IM/4gSBP6urgT9FXII/Kg6GP1o8hD+QO38/lkR5PxPdgj8HZ4A/rK+CPxUfeT/X94E/kF56P7Amez+X8ng/eHh9P1N3cT/wZnc/sDZ6P7BFhj8t238/iXWGP+pmgD9Ug4A/qid1P6PLdD84bIQ/AYRsP+Isgj+1PoA/1JmHPycYhT/IWnM/YtVjPwfeij9RyOg6iNrzOzYWrD3TzcG7qYP0u8xeY7ygL688SH0KPNfxrz1PsMe8JAdDvanxK7xz4gk9944jPOKcdzzKw0M9MIyMPc19Oj0z3Gu9PZFtvJAsRD3cAbw9fgxkPA4D0rz2CDc8JVhEvIlVDj6wSAc8WgEMO6CSVby0PS49iuv7Oiwl5r3rPKy9OUVnvCyxuzzbHMA7gM96toaI2rucOS28gYxNvPg1kbx026k9LzPhvEHJn7mI+/08eD+PPcw02brFyxY8Kw4iPNYcmbwZ2KE9QGD8vJ3r6D2j4Js8HohWvMisBT1ck4w8w16QvBNQPz0qQyg93lSQPNMGt732Tta8","lDowPMzJVz5jx9w9HpkOPpZhNz6h0By91t+xPa3KUT51OY0+8R2BPj/vTj7WXro9qkzJPS1KxD0nr6c8tqZbPcq6OD7MMRg+GNw5PZjd2D2Uilo+dPAQPqth4j0B+kY9Fjm5PPT72j2t7K0+9X13Pnph3T2AErU82LmeOva91z2Sav49i87+PSoDbz1Tfm490hYsPphVNT0B5rs9ZJGEPlVygz0bzYU8hER9PSpM2zwFd8u7aZPCPYciED4w01w918wqPdQa/j2ZN6c8Y5UxPNZt9T1K1c09tkQhPau5/D3z8sI9CwhoPTUHvz3i7CY+9b9DPn3oND6rmcO8hjT2PQ=="]},"dense_1":{"weights":["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"],"bias":["6R8Cv/z4gz0oRJY8+wYePvriF720c0Y+iMy8O4OwXjyvu4Y7Hp8IPgwLJryc/xq9RIZzvx91mr/CgXi/8uaRv9u7gzzUG529n/SUvcZobrpMZJa+0UOzvcChG7+w0TK/B6GePHRTHb3cH76+KPmwvvMm9b5Uvju/7v6Hv2ICM79Mvxa+EZuFvmUjy74o6EC+14LDvGE9pz3lZJE94jzYvqDWlr5FAAS/gr6XvgXm8Dz/beG9LXPBPUk8xz3YUTW9McKQPBICo73IfpW+HfSVPYtmAL3fTRY9Xl1/vgnuJL56FaI9AwgGv7vdiL68Roi+"]}},"hash":"1e4e72afa03737742b8f7b41c365b2eadcfd9c51d031d633ae9ae7f0648f08e1"} \ No newline at end of file diff --git a/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_encoder.ktn.model b/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_encoder.ktn.model new file mode 100644 index 0000000000..398a6e8f14 --- /dev/null +++ b/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_encoder.ktn.model @@ -0,0 +1 @@ +{"architecture":{"class_name":"Functional","config":{"name":"model","trainable":true,"layers":[{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,18,18],"dtype":"float32","sparse":false,"ragged":false,"name":"input_1"},"registered_name":null,"name":"input_1","inbound_nodes":[]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense","trainable":true,"dtype":"float32","units":64,"activation":"linear","use_bias":false,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,18,18]},"name":"dense","inbound_nodes":[[["input_1",0,0,{}]]]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm","trainable":true,"dtype":"float32","return_sequences":true,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[null,18,64]},"name":"lstm","inbound_nodes":[[["dense",0,0,{}]]]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm_1","trainable":true,"dtype":"float32","return_sequences":false,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[null,18,64]},"name":"lstm_1","inbound_nodes":[[["lstm",0,0,{}]]]}],"input_layers":[["input_1",0,0]],"output_layers":[["lstm",0,1],["lstm",0,2],["lstm_1",0,1],["lstm_1",0,2]]},"keras_version":"2.16.0","backend":"tensorflow"},"image_data_format":"channels_last","input_shapes":[[18,18]],"output_shapes":[[64],[64],[64],[64]],"tests":[{"inputs":[{"shape":[18,18],"values":["qMkQPpYCRb/LU1w+YXgjPzfHpD/W4yLAD0ixP9eppL/RYbQ/iYEPv1TMTz9/Em8/F7SSPhDgoL9x6BW/7HYGPncH47/Jg8S+FWlpvzB5qr6NUmG+kq9cP1xnS7/gbp2+8wjRPVnFD8DvwK6/JrCMv74vxT9VKYc+ESDOvjq7pr+8Yl4+IZ88P52X1z4ezVtAs7A/vo9f3r09Ak4/GxghvqTs+L/RAku+bunwPl6d6b4LQ9O/aDMaP9krHz+BdBi/0SWmv4lavj0snR8/qK7TvnRc4D/kPgjA4QKMP61yhz+kjY4/cq34vvtsyT/SthXA0BoSvwly8L6FzVG+3YNUP8Ibjr/oQU2/i9olP4KKib86iwm+Kw70PpwsLD/J0/W+zVVgP7S49T5hSsG8jjwzPye3Wz4QGw++RxFCP+4EpL/MG7s/TG+4PkWkSz9Fgw8/TGvGv4lEAD+psAU/PYmCPhg8AD+wtq8//9VVv2lMvD1BZTC/NzOCv4C7WT6Gltu/0tETP9wpRT9SfT8+A4GMvcVZK782EKS/85CWvAVpmb0mHgq/UO6kPz7J2j/oJIi/J8W/vx5wWL7dmDU/3txFv9zkwT22Ek+/+bdMvz2PHT9vDs2/QOX1v/Toyj6iTau/JmkbP6DGqT/on7c/zjM+Pi+9Kz+/0Da//6+xP+uS9z4gwqm+vcRivuDBAMAG8oo/GPkfwLi9Xj23IzC+1LkAP19rzz4DhPk+1MXDPj4+NL8aJdm/co2UPYU9GL5TqvW+6A4NvnwljD8xxge/RsLlv4zZ4b0xqSG/7V2ZPy2T979sRuA8mEEWPkpTN788Nh4/LVHivT2c7b80oPo/clAGvneMhz7p7ck9MNkMvxzBZr/vy+M928oFQCIgfb8r9DU9Xeorvr7TJb+U68c+/QQbP6PMST+Y4D4/JU7pPsAMVb8wuqM+kqiZP/8YcT08clo+1OBbvsAqHb/PvHO/qAerPulDxT/NOq8/9XgUP3+ror9xEQa/mEDdPHBBID/AtZc/","Pld1vxATmj5HrtW/HbcjP/tzST4wpw+/2+UBQAfgXT4bL8i+oJd0vrIUEb9stho/EuVBP91LYT+axlW/AnbPvraK8T7lUzA/iehNvoy7Gz/vTOI9/bbIPVB8SL4jM8S/pdJvPo9cIj+60IC/GA4vP5R7nr+XT0c/AgHzvBLt47/F+yM/UeuNP+i7tL8LsTK/E+YYQOedBL2Y4Bc+uINSPzDfzr6fbra+PcwTwPc8jr8FuVa/Rep+vsdSYz92s2A/+wVYv6YtiL9P4ck/MZkCPyAUtL8/qUy/vHuxP/Z6kj8B5wxAvr8gvzn85T/2RR6/m9pQP3fnqjy+zwVA7GDBv4V9fr73pEM/BhVOvx/DcL6f2xhADI8Xv+xajz+X9TA/wTBRP/nvFEDvoue/Xm44P2YGIzxptjG+55FIv0zclb50sYa+I5p+v2DIKr7LUhG+rr3KPbW1iL+QLCQ/7+ulPyvO1D62tgdAzMwTQGBB2z9gNcy/LL+5Pk785j8snEs+o4o1vFJTrr/bBq4/s5lmvi++nz9Mzb++NseTP2vnHz/BjUo/LGruPoDbqr+sHAO/H0wGQB38cz8evFW/s9OjP64qob/WrlG/DkInvh2cdL97MlC/KlY1P9aMOL8Tni2/eBziv2T7yb7kdPw+GNW+PlWNHMBOmZM/Ze6pPwl2xT19Eqc9G0d4v/TTu77teuG9"]}],"outputs":[{"shape":[64],"values":["AEuROp8dpD5Etl0/xV1nP8Q+475fnmE/NiSzPLJpa7uyayc/MUvrvFSeAb8fjYe8G4DHPu7t1j6XD6U+nCLEPfowdj8EPjY+wCIhv2KzGT1D2C2/wg7APaM/NL9sRi89Idqrvi50wj62O7E8l4ZwvxbkXT9VFG2/aZ0jPXVuMr63EDe/qS5rPNgWdj+5eUW/bNXOPunHhz6rDNw+nExtvyOhUL8XDHQ9vDBKP/zfH7y80U88jodiP5JrSD+CQEG8ywIYP4Q/Cb7GKUm/L1mjPj0L2b0AogQ9Sw0mP12fkrvajly95sk0PiPgSb/XdP07NbdmP5PTKL33vs88P3GqPg=="]},{"shape":[64],"values":["96UkP1Jllz8IwxlAzMM4QBGM/r5nQQtAIJVCQJKAs7/4OiBAT940vwBi6b+cNVnAYGPUPuR0AkAkluM/TLYTP36TB0Cgd5I/uExXv65G+z+AIb2/pkDKP2RO+b+At1E9SXSOv6Xz2T5oFRo/sisJwMRFRUBEfdq/Wss+P7EDhsDOf7G/6lB8P7goOUCFjjLAHew1P/Jqwj+5Z0VAHGMCwCbIur86h48/04ObP6Ddr7+4VApAW/k3QNOGrz9mEzfATltHPwvmO75kpaK/RgbrP+sTI79xIIo/T5d6P843974fnfm/Q1RdPoyTx7+op6I+2AT9P36l/L/8jeQ+mkZfPw=="]},{"shape":[64],"values":["Q6kYP91R0r4hj7a9TbW5vk3Qrr0nMgO+fjdRvkayoT61Acs8ao8UvYfN874FOhu+10HGPSEdDr9j0us7Rl/1vWwJPj7SGg48njoYPiTt+b1XVxs9rFIsPbVcJr+6Xg4/BvWCvvMpf77c+wU+E2eGvXi3N76pCyK9YERIvgPRiL6bm5q8xtuzvgcWIT9hSwM+y31KPW6Ear22xDE9G+7ovtheZT7gQg8/qT+MPDyoWL47VLK+tKQEvvvE/z4+/vq9FcaJu7Cj9r4jv2W/MjrSvoNbob7Rdb0+5D5RvMYmID2lhpa+SxYiP85LSj3qITs87uoYP0eS1D0tMOG9sXu8Pg=="]},{"shape":[64],"values":["IGi8PwtxAr+Ci/69IRcHwElKDL75Wz++iuqTv3CHsj4bplQ9FghjveevOr/A6yK/iwcePyPWXr+doNI9bwOgv7ZGTz/0NWk8pbAhQMlGlr6i3tI9ht9NPTVkGMBBqjtAqFyWv969Eb/C2CE/PU63vUymnL9VZTK986IOvz4/FL9sfDe9Y1U5v2ZxlT+w07c+eHZ8PXoDxL0wOf89FSgiv8g8nT6ewWk/qoqKPX7LoL7OLeK+RJs1vn6VXD8QXBa/m/vyuwYUxL9U04rAsLAFwPg75r7PTnc/jAgTvd0yLT5nqwi/sF2bPxmyHj5SWAQ9S/eJP0Z44D422Mq/LXIAPw=="]}]}],"trainable_params":{"dense":{"weights":["y2qOvcW3pD4xKrW+l+oRP8LIhT5foSQ/JSmiPp81ET6PK+Q+vRa3vuHWur2AaaW+9cigPsFxzb5w/hu+8Sk1PqQihD4HX7O+MmRzvt8BSL55i6g+dcrHPV09Vz63YJQ+uB6LveqGsT475fC8Z9P5Pjx37D50eHE+Pv/lPmXPCL2Wk9e8O0uDPgeqcj4Sp/6+bzbJPDXCn7shFNM8ETH+vY4YLD6Jk6a+bs7iPuljwT4tT0S8fIiVvriDjD3y1LK+zGiSvqRxvj6Zn5k+rD3zvjbgnL5qGQ09YmO/PhFwID/lQa6+G816vMVdiT6qdKS9x58UPiP5E75Plw6/Oqg2vpRMcb2P1zm+eEYqu5Ko0zuVM4G8b8j5PLg8EzvVd5S6ui8dvX31Qj4rKRy+hqgbPrPsrbrzkEE9ERG1vN62ub0UpEQ3pEwTOklLQr4mRko9FghCOZWAaj2Gm0C+qjX0vVak7L1Y5/Y9J2X7ujAKgj3CMtI9IRk5vmBPGz3huFs9BtWzu9l9DD6tdCy6L+9gus89Hb5pQsy5HMXIPHPosz0CUx+8HWo5Pln4QL5pR1q952CkPdcgoz1OSgQ+m4qGPRDkgzzxFrE3AjuNO0gpMj4B5Bg+Yek7PpvXILn+Fp06IruDumNCJL6hSKs8/VraOT57+LwzoCM7jBmavIF2Gr7tw9E9+hIKvs/N+b11CSk+wqIpvrxhZT7B4S29VQv0vccFcj6KKHo974AGvVQzP75Opnu+Ma0LPv4vu728VCe+wpADvcrSlDvHykc+P1IOPv7eVb6hN/49NzS2PcPnhL6yBx0+fLeOO82VP74GDEa+wdKZvZ7YMT2ZUVa+FWR5veDGkr6e2GY+NLaYvXelY72Km4K+ryA3Phj7vz4DAaU9lX1RvsGLnr0kOEq94a1mvKKQRb3pDJ69x+AJvmWamz3S0aE+xDDXPe7gKj6mnFQ+cLehPattdz7kQoS+wmwHPin55L0ByB68Lrr0PaLM/T14qC0+XbcIvYunAjzNs14+","mTBjvqq+iD4R1kO+xwcivId8JbyMj189D6fSPVMPi72//Bw+QnMKPYTBdD5RXwA+B2Y3PGgI+bvqa3m9O5MSvjr0BL4HhT4+0q4cu24MjL1aYHE+q9XkPXdZcb1QX8U7jIwjvltWhL1j9B++n58Svm2hOb5AZCs+bC/ePY+t7L07pRq+4SN9OzrGCT7rIfK8TkeDPhCiHz7oQsQ8VIiWu/SrTj4EfPU8qip/vR03Ez4aSm6+RohmvhiqQL6VDgo+B9lwPkWHFT4Ag6Q90zRnvXt3gL1WmRU+pM8KvkETjz2xRAe9+9LGPVg3LT1mMJo9qYN0vmxMWjxgnGy9GA2BvmH3/b1JHEo+MOm/PVJUsb1s9p49nGATvJkmnr3xtQY+H5DKvfAexbz1x489fcuRPWOqCDvVhgu+TKU2O8T0jj3vSpe8/3PuvZ771rrlpQ8+NgaRuBvkEj7fDcK9wT8ovkruyjy5lRi+x+0Uvomk7D3dal44xrt9vb/e37ky36E9bb8IvUgtrT21ilK6A/S5PHOwVb21TN09mBwpNzlEDj5gl4a5K+OgvbKvBbvjk7A4fvmpOluFVL0r678949C9vaiH4j16Pug5UcQkvoxLFjpfX0I95wvbOsihUblpMJ48ECUZPrNmUjv31Nw4DqcJPo0nMT6h2Vc+LQPsuXmvsLk25Do+fZvbPRK3cL12Ipk9guqzPXQrpb370sO7+RcNPYgjVL6px9S6IBgXPSoNGTzoQQw+avvhuvCvyz2YO2a4Y3YXvZnLfbqIrZe60lZDO8Tb1DrQLRc6lVe2ullDqro3T0G+sQKIuv7HY7sjKwI7R2kqOtZ/4D3X+ge7ZXb4Ofy++zouGim+adx6u1vlg71lm+S92YNGO9oT+jmNFz46Gnd2ORtf/rm8O8Q91rHgvVKBA724FDQ8MJirvRvUvjp7bnI7sFotPiVr2LzV7ue5Go09utM3ATn6Y8u5R9GfvIkLdrzAQ+U64B1dui/sgj16T409b0ZvPIYqibphlC4+","3pXGvU/EPT21yCy61DE1Peou5ro4Op49eddCvuiW+z198989XpLSPYqMxrqjJ/W5Tq+PPXSyGT5zOQA+DqUaPTyXCDoYRj844GzKNnmJ+j0PcjW5VcSmORQToTyubWk6PWY5OwbuwzxACAU+fTq+Odmfs7qn/sc72OnPOjCwxTkptbK5GrkMPsl/Tbu54oS6ZzxqO9oB+Lrc0TA+PeFpOvKeMLvCkpW9Yb+JPU+qrj1LKre9P5USvJDLkzeb2xO6sk4kvivak7159FG+r34CPje87LzeuS09QOuxudPMYr0a1Iy760GcuYJuGD6Mn8850DqpPYsic74FssC5cAE+Pk0fxD3Mlzm+0IVbu+hzmz0KfYC+pdhOvg3cFT7Gvzc+i6vMPeatL76VlWk+QG5sPQ2UID7JXZQ+ZExYvonvl7xzW/S9jCKJPdIJnj7n3ZC+jWMuPrapW72592y+r8tLvqrkrj7Caha9sNmGvrvRTD4Gn9M97QU3Pkc2Jr3kGJE+K72qPF3Nkj7u1DS+14nPvaxYBr6egaA+8oKtPaihLr035wa8hc2ivcHMST7agxc+UJ4NPukgYz3Sd1S+NB9XPuNt6D2bsBY9G09KPsVhYr4bBkG+zN6Svs0zUj4hSmy+77XRPIopQ737AQ89F2yfvU7XHr7+Zoe+P6A3vuYeJD4XGKG8viMYPQZgQL6V2IG+vDZ3PiL84DxNTbS+v25UPhreET7XfCi+eaF4vjWNCb7QOji+54rAPbwPjL7EeLS+ZkB7vimklj4j4S++nv9fPqhylz43FoI+WmWUPmJfQT70bXW9M96GvkN3f72BIGQ99YKPvd2TYj0OUa+9s8DLvf4N0j0yVoa+FIM1PW9LuL2KhR89zqqyvhX+8L0M7le++CBCvvyZqT4KWJK+l6ZLvrzbkL6u+ZM8B0tEvTQnsT6dHic+NSyGvk6DHz3IwEi+ukswPVGiEz65zAs+8UmFPfu7wD20tw0+fqRkvrciaL5Oi/e9elAcvaTvoT0aXYq+","PmaYvOxhjr57QOQ9h7aAvSqD1r7bAZq9CEBWvUOEDb6UW46+kKKQPszxi77ebJq+CyY9PZfCk76ThE8+hstmvehHlL3HXf89iFfYvTL28L4QjSm+4Tb4vcbhMD6JkK6+ZrYkvmNhcT0MAW6+A9C4vYXGD77SLwk9jwV7PivbyD0bEvi9z7VCvjgml742D3i+rZqePerAOD6af44+eLtdvuSms72HOkK+Xf7uvv26ST7QeeO8M9joPdkPBj7w8Qu+9LuePvst873FSlS+yvlbPrUaST7WKdc+L0ievVBljT3DJAw+kxcZvnLHnzvD8Ng+MN2WPVMcYbxPnf8+dZcFvvOjAL+uvLe+PrWyPt4jID9lbVO8sFTSPU9wMb3JJE+9Lh61vnwOJj1ZCmC8JWEMvk2Kjzw+q549S2ayvhKJAb4Mctw9a6hAPvaakz5WeuK+cQ0GPpQXKL6FD+g9JdUKvNCy0b633+Y+Ou4UvR+cID4cWv+8Y+1vvsx2Yr7jApK9pGo1vlQ/lL7ng6K+A4VHPRQGAT8xuva9eg37vbXJyz3AnYA+lspBPcnF5b4T+2m7qMrBvNPaDr3etZo9EEizPj643r0i6ia9Z625PdEgNr4fIYE+XMwPPi9KIr4pu6w+Fc6RPmOiZb7W06i+EFoTPmlRur0QxAG/MPamPqdDlr4gUqI+kRq3PsPNpT0bq4u+2uLBPntSZ76zLTS+lLlyvt5E2r6BZk2+OTaVvYsF2rz+0PE9dIaaPuY4nz6cRMK9BJElvmpWs76sAs2+POqAvR8JSDy2O0S+8boWvSJ2UD5Cd0o+Y/U7PcEh973I0ma+AKORPthxFL6OLWc59oCYvhFgz76taj2+kD5RvJv/gz5cUGe9ZMDkvZr3oT4DoxO+N+qhPs7/Jj579N0+wPThPvzINr0EqJO+Cx/cPj8VLr6GIVc+OyFqPtL7BT7yynO+9SwBPgy2uj6l8VC+svNaPrqohr4So5K7J7fivXqIqb5fpKE+HCUVPLLNij7y4Sa+","RdZDPdFUqj5YMZO+UgJxvu0eez6DLBS+q+d+PiBwC76H9Ly8EGB/vqm2Mj70Lpc9hrF0vahNXT4GJVE+++GQvtACnz1c0g++8xAUvjilqb5+t9M+OruQPdOLvr1u1e8+HJ+NPYWDZ746cR29qMySvuXUkrzggwQ+j+2sPVxrlL4jLYi6jt9Dvq5pfb2jXsg+C5ctPmU9XT72qrI9YGKFvXsv3D2AEeM+xTSyPoVWsj6HPCU+7U8Yvm5owz6Bb1m+JVmIPsb3Tj7ebik+NFv/vUFtrr4LPs4+EwGyveOotT6nFL2+xP1rPthWLb4dyw+9QjuiPWOidT0cQq8+9hspProAhz4Bt6+9H/OsvT/xZbxgxgC+e3kBPnLUTT2X5KW9R6qvO5J6Br1qA/K9Sj85Pq75Tz1VLGe+0ZUsvs6GLj5YA225DPkyPat3jD6RRII+5CXMPgAfz74+Yn29FupyPkXwLT3mWIa+6nJlPl2lzLxu2Yk9hCrsvZ98qD0y6WK+TqN7vVWCtbyAqm++DagePSB5Ob4G0Yc+8tCYPrIJKj6FQdC9LtKsvcTEDD6pEoU8xxzzvS0/g77sema+p5dFPtkjM73gWLQ+bjh1Pn5NYD4g7Rs+TKYkPt7fWj55lI4+K3m/vZQOg74xOOY8RgsYvaOl2D2+zSu+7BM7PvS7vr6BSWO+9G2BPi1Bg76Nvyc9/Z8Qv5GCQD44pU++6B23vvzNjz6ZR8c9VKa1vjJ7sj5dc8Q+tduVvvLOmr3Rnzc9ZpTvPjmOKT4RffO70H7CPQsKRb5ECGS+eihYvpXzyT0BN4G+KrnEPpAl6j2f54q+qP91PjP4IL4inUM+lTIkvjVcjj4JvQE/sDZTvXFxdL73RmA+WIdJvqaKGD5/G8w+sxcTvy+f4D620kG9cgicPX8+oz5YRya8vjeePsMQHb5DuFa+Z0VfPk4fkDyHR7I9xHdtPisYJz4nw7K9igMIvZ5TVj7ftp49Sz3vPiWWzD2vUTS++gGTOxESTL5vbQU+","1AxpOmKYAj7yxHW+/CDPPZQzXz7H0Yw9mBSzPUqCgT7QWgU9YxeFvUGjwr1VuIO+3Y3AOb15fj4tq9m9iSIJPkX4Sr4o2kK7VZikvvvZPr2yDwa8oJK2OehK1jp2CT2+No/qOp99TT4jXEE+YIJcPTP0Zz3oU+0955iyvnTGHzrQgwM+UoZlPcIQOj0Wu/s9Sy6qvZNgQj1Y1YG9IHFYvp5spjs4AYG+zDHyujWtiL7qwT89OM5ivMBwhz0F2hM9kyQAPjV59LoZM6m+V1J4vr7mHr55ofq9oe8oPqjTwzsGedg+g1XqOiaEi70/tq8+Vh7PuQFm6TzgSXe7+XyQPNnbyL4DE76+zVHOvmnovz4M44q92VFyPmMDvj4t4yW+oHLyPjyY0D2ctcU+ru2JvgAcDb5PWNo+uV/OPvHpkz5QMw8+zJfbPu7fiT2q3Sw+muivvs9u/j66woQ+T9l+vhmnDbyT74K9C1SRPvCmZL5gp22+hveQPv0oRj5+ipc9PPnkvlqMDb6U/6M9nkXEvhB2oT2R/ci+60L0vgSu0L5iNJy+CbgrPvmB1L5/wWe9a8ynvo074L5Q1oO9GHknPgcKyz2BEva9K4CtPkQ+zr7NLr6+BLpVPee3ibzC/oO+7hEZvqBVvL4ED9A8rJOMPgfVtj7UpZm9DdQkPtyZjb0CoHC+JUtgu37gdD7W0w2+trJ/PmYZSr48Pn4+JS3ivUnuzDvHt3g+vP0MPn/DTT2BoAy9aZdsPLVSm75N9Tg8MxK/PUQt8zqjuz25XwF7PRxYTL1h2xU+YnxovqIsLrx39Ry+TusWvPT9KT7iIme+MsiCPXBZkT50wty9dezlPQ/K8z0+/KW+wRUzPsHRgb43c16+956sPvnptz1anzU8fxh2PtKw2z0gQXk+aB6PvP+nNz3qtYk+Th3pvfZqDDywPpY+HBllOwTXHL6kdRG97VClPadAdjzoWr49MSCcPhqQnD2OuWo+JPszPi1EA73EKOo9fVJgu+WPm7q1xkE+"]},"lstm":{"weights":["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"],"recurrent_weights":["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"],"bias":["6KuYPi36vD6E/sM9pgUvvNFOuT0UACg+YdogPkwQWz7QKQw/6NycPgs5Rz+qIkE9mzIXPnoGjT+JI4k+ExwMP9vxDj40gi8/b4EivTfqtj/C970+P+FYPmUQnT5znZI+JS8FP5X9Ez9PKzM+RlrbPhNfcT2u4ak+cSDovTk9Sr3Hcoc9fW/yPd1ZxD3lgvA+emx/PDsi+j5d/C8+IIxhvUEhEL3/2oU+YEuEu8qGiD7HzUA/XdqHPnjmbj/E5QM+WL66PSXKAz0mTV49H0C4PZ/ujjzkSdg+kGHWPrkzUz28UQg+w01CPtfqFD77sTs+UQgHPtADUD90IEk91oeGPuTnlT/GQ40/bhzCPyazhT8J4Y4/9eaQPyXciz8puZQ/o1eOP9tHjT9H25Y/FOuHP2IiiT/wcJY/+mGQP31ijT92+KE/HiSIP81Plz9nxpY/00OcP2mCjD+4k6o/eqiWP5fsij9o1xg/cC6MPzjkjz9MaJE/BRmHP7thkT+UKoc//WuEP+8qoz+uk5Q/mzaRPxkWpj8HtZM/Q56VP3sYmD+FlJU/HoWMPxQ4hj85n44/1FGnP4TdjT9mYpM/SgCXP/08jj9OTpA/9piGP+BPiz/3bHI/bseUPy2Qoj8XYpw/ZjKOP4VElD+LupY/R/qLPyqKnD+35JU/hW6ZP6Urkj+8rn69kilEvayHjbsPibi8oZH4PEqCxLxBRik+TG7COx/qHj/EOJg/YjOXvX//bD2aCiQ+ce3QvaFgmj024RO+Zde+vL2Mfj8WZj89uVeLP3F99b1naom+OoLdvU+4cT42RmM75fvsvYK8j72Qfxw+0u5pvTZjZLzyKAQ9p+AevX8nOzyFhb89FpVzPDTmDr96Zni8ylICPwrcX719U0G7BlkXvt+VOD4XmKy9ANZsvqCWfT7ti4S9jW9XPmgq3DuwZ/K9+TVuvo5b4TztjqQ87uYqPiDAJz1IEU482ELlPLQ6mD1FcdM9630XvcTq/j24PaW9kYU/v/1V8jol5Ji9","TVdNPhZZwT6fBYE9T6PHPQvjFrwbTuU9yfJQPbJB4TxB+gc+NfaZPZIsPj68J/09mSFGPr6oCz22TYs+V00avZQPUT7RdsS881iiPaRJRz5fkUE+rXsTvbtWWD6XjZA9NbcxPkboWT5V3Bo9FA+2PpFxVT5rlaM+EfzYPa1NaLtXgd89d0R5PKqhWT7n8BY9xAfxPX3EoDxEiUA9qprRPhqWNr2j3s09iR15PvfW072Aeb49yulVPoMCIj7exT+9C9r8PrBurz2n26Y+Cx3kPNVTyrwTOwA+vRvVPP96Hj2GGte9FbwCPX1UZD68KoQ7DOirPrSuGj0DE/o9/zogPg=="]},"lstm_1":{"weights":["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"],"recurrent_weights":["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"],"bias":["DPVNPRdSkj3uFa+9UWw5PdA48T16pqU9BVU8PTC+eTtFG8s97/2ZPVYCvD0cS009GXR0PYTbL72zzxU+9vHcPX7GmTz4l9s9MINkPWp0WLwZbrc8bqqFPRbUFz6diZw9/8azvIh9x7zP++i8Q9oAvevX0z35E+u8BjQAPk8EPDyvkp89zSmNvRdufj3l2zY9Mt8MvhbHyTwb/mA9gxlSPocCLrymJQQ9rssAPqWlET2/UHa82vRPvQnkBj7faYU8FA/bPfVZGj5DIWw+ZIqAPT9hS7wbGNG9NN5HPcWDPj1vPBc+xuTPPaQrhj16TII9GdbIPYPHPz3asMg9NF6kvJr1hz8g44Y/522DP01xiD8jCYs/H6WMP83biz9TYW4/Y72NP1xCiT/7So0/ki6LPxtNjT/N3IU/WwWJPwmShz9E/Yc/vD+bP40liz9VVog/J2aMP31NkT/3lYg/V82OP1Xviz80qYw/1zqIP/MdhT/1OIQ/KACTPw6iiz9LpoU/SJ+KP9Yfij8Guoo/BdqPPwDmhj/qtoc/6kGJP5jCWT+Fu4U/AX+PP6+PiT93O4o/zJCKP1G+gj98UYk/mT+HP5c7gj/gqZk/hpiIPyL0hT+XVI8/tJyFP/4ahj8q3I8/6dKTP+iRjj+ew4c/GaeNP2itiT8wBI0/8Z+IPz+Zfj8E/Yk8CO9YPWxEz728ThA93osWPSeQIz2OVva7XuC2PYOP1LznUrs9pNgFvK8LAj3eKGK7rlJsvLPAI7tcgOA8/AQnPUcAQr0ktmc8cguJvWyqCj2pY6e84vWwPM86w7w2qSw9AuetvWRDiz0XJ629ZHYXPZHhaDxEvnK908pdvUBYEDxsOkC9+7+JvXCXFb2T6MU8UxHePEYRrj3DoJE8Vv9jvfjLFjxbYUe9zwYkPbFHEr7xASo+F21SvT3OOT0LN+i91duRPS9nwb1htrw8DSryvTfYxryWQQw+E9JOvSzg7T2RGhi9KHZkPfPwNr1xHI29x+I/PfOMjj0quRA8","fnRsvVk9s725BLi9oyf4OiFK07y4PbK675fLvJDOpr1X5MQ9jhBqO1Jca71Tg1U92baDPfYECz2M9P88vUdcPF2iqLxfDiQ9vLQfvcA2oL3CwFS9y/6yvXxOVD3kprC9icHnvAe4trwDkrS8fCGFvW6TCz7U4r29DzlxO1tAhb3J/dK9zahlve4pC7zuv5q9k8rGvW+2aLzcHTs82TIqPh1Cm7zucfU8ZI47PSxgy71Y7qC9STX9vb5zHj3oXdI8/NezOwWp7TvGC7e8Qs7QPb5neb3Ikz48fuQfPqAeN7zfq8i8rgxbvLeYfDyy/6w8xOv6uxj+Tb3BDuQ95v4DvQ=="]}},"hash":"74c98f52358fb031df478a65fcad930c3c2e9d690fd5b551ec14ffe531fea203"} \ No newline at end of file diff --git a/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_metadata.ktn.model b/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_metadata.ktn.model new file mode 100644 index 0000000000..b0d146f016 --- /dev/null +++ b/src/kernels/gfx90a_ConvHipIgemmGroupXdlops_metadata.ktn.model @@ -0,0 +1,71 @@ +{ + "num_tuning_params": { + "fwd": 13, + "bwd": 14, + "wrw": 14 + }, + "decodings": { + "tunings": { + "0": "0", + "1": "256", + "2": "128", + "3": "64", + "4": "128", + "5": "64", + "6": "32", + "7": "256", + "8": "64", + "9": "256", + "10": "128", + "11": "32", + "12": "32", + "13": "4", + "14": "16", + "15": "8", + "16": "Default", + "17": "Filter1x1Stride1Pad0", + "18": "OddC", + "19": "Filter1x1Pad0", + "20": "8", + "21": "2", + "22": "4", + "23": "32", + "24": "Default", + "25": "Filter1x1Stride1Pad0", + "26": "2", + "27": "1", + "28": "4", + "29": "32", + "30": "32", + "31": "1", + "32": "2", + "33": "4", + "34": "32", + "35": "4", + "36": "1", + "37": "8", + "38": "2", + "39": "2", + "40": "1", + "41": "4", + "42": "8", + "43": "1", + "44": "4", + "45": "2", + "46": "8", + "47": "4", + "48": "1", + "49": "8", + "50": "2", + "51": "4", + "52": "1", + "53": "8", + "54": "1", + "55": "4", + "56": "8", + "57": "2", + "58": "-1", + "59": "-1" + } + } +} \ No newline at end of file diff --git a/src/kernels/gfx942.kdb.bz2 b/src/kernels/gfx942.kdb.bz2 index f781d5219c..809f1197f2 100644 --- a/src/kernels/gfx942.kdb.bz2 +++ b/src/kernels/gfx942.kdb.bz2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a92fc8a46493a9b3db5f7ad6d4d8da16c2084e0a149b8f487f3bc774ef2ac421 -size 684230 +oid sha256:ebc612b797204cab3150a788dad0fda1a79d309fc94679017007d90f776b6f15 +size 1729359 diff --git a/src/kernels/gfx942.tn.model b/src/kernels/gfx942.tn.model new file mode 100644 index 0000000000..67a463e075 --- /dev/null +++ b/src/kernels/gfx942.tn.model @@ -0,0 +1 @@ +{"architecture":{"class_name":"Functional","config":{"name":"tunaNet","trainable":true,"layers":[{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,18],"dtype":"float32","sparse":false,"ragged":false,"name":"input_1"},"registered_name":null,"name":"input_1","inbound_nodes":[]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense","trainable":true,"dtype":"float32","units":64,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,18]},"name":"dense","inbound_nodes":[[["input_1",0,0,{}]]]},{"module":"keras.layers","class_name":"ReLU","config":{"name":"re_lu","trainable":true,"dtype":"float32","max_value":null,"negative_slope":0.0,"threshold":0.0},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"re_lu","inbound_nodes":[[["dense",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_1","trainable":true,"dtype":"float32","units":128,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"dense_1","inbound_nodes":[[["re_lu",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_2","trainable":true,"dtype":"float32","units":128,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,18]},"name":"dense_2","inbound_nodes":[[["input_1",0,0,{}]]]},{"module":"keras.layers","class_name":"Add","config":{"name":"add","trainable":true,"dtype":"float32"},"registered_name":null,"build_config":{"input_shape":[[null,128],[null,128]]},"name":"add","inbound_nodes":[[["dense_1",0,0,{}],["dense_2",0,0,{}]]]},{"module":"keras.layers","class_name":"ReLU","config":{"name":"re_lu_1","trainable":true,"dtype":"float32","max_value":null,"negative_slope":0.0,"threshold":0.0},"registered_name":null,"build_config":{"input_shape":[null,128]},"name":"re_lu_1","inbound_nodes":[[["add",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_3","trainable":true,"dtype":"float32","units":256,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,128]},"name":"dense_3","inbound_nodes":[[["re_lu_1",0,0,{}]]]},{"module":"keras.layers","class_name":"ReLU","config":{"name":"re_lu_2","trainable":true,"dtype":"float32","max_value":null,"negative_slope":0.0,"threshold":0.0},"registered_name":null,"build_config":{"input_shape":[null,256]},"name":"re_lu_2","inbound_nodes":[[["dense_3",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_4","trainable":true,"dtype":"float32","units":64,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,256]},"name":"dense_4","inbound_nodes":[[["re_lu_2",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_5","trainable":true,"dtype":"float32","units":64,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,128]},"name":"dense_5","inbound_nodes":[[["re_lu_1",0,0,{}]]]},{"module":"keras.layers","class_name":"Add","config":{"name":"add_1","trainable":true,"dtype":"float32"},"registered_name":null,"build_config":{"input_shape":[[null,64],[null,64]]},"name":"add_1","inbound_nodes":[[["dense_4",0,0,{}],["dense_5",0,0,{}]]]},{"module":"keras.layers","class_name":"ReLU","config":{"name":"re_lu_3","trainable":true,"dtype":"float32","max_value":null,"negative_slope":0.0,"threshold":0.0},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"re_lu_3","inbound_nodes":[[["add_1",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_6","trainable":true,"dtype":"float32","units":64,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"dense_6","inbound_nodes":[[["re_lu_3",0,0,{}]]]},{"module":"keras.layers","class_name":"ReLU","config":{"name":"re_lu_4","trainable":true,"dtype":"float32","max_value":null,"negative_slope":0.0,"threshold":0.0},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"re_lu_4","inbound_nodes":[[["dense_6",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_7","trainable":true,"dtype":"float32","units":32,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"dense_7","inbound_nodes":[[["re_lu_4",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_8","trainable":true,"dtype":"float32","units":32,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"dense_8","inbound_nodes":[[["re_lu_3",0,0,{}]]]},{"module":"keras.layers","class_name":"Add","config":{"name":"add_2","trainable":true,"dtype":"float32"},"registered_name":null,"build_config":{"input_shape":[[null,32],[null,32]]},"name":"add_2","inbound_nodes":[[["dense_7",0,0,{}],["dense_8",0,0,{}]]]},{"module":"keras.layers","class_name":"ReLU","config":{"name":"re_lu_5","trainable":true,"dtype":"float32","max_value":null,"negative_slope":0.0,"threshold":0.0},"registered_name":null,"build_config":{"input_shape":[null,32]},"name":"re_lu_5","inbound_nodes":[[["add_2",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_9","trainable":true,"dtype":"float32","units":20,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,32]},"name":"dense_9","inbound_nodes":[[["re_lu_5",0,0,{}]]]}],"input_layers":[["input_1",0,0]],"output_layers":[["dense_9",0,0]]},"keras_version":"2.14.0","backend":"tensorflow"},"image_data_format":"channels_last","input_shapes":[[18]],"output_shapes":[[20]],"tests":[{"inputs":[{"shape":[18],"values":["eMzhP2jhzD6Tjno/y2oPQCQM7z/iLnq//zhzP2L9Gr5oZNO9+DnSPiiAEz6iJbo/XtNCP8Aw+T0LQuM+XdeqPvw9vz8CFVK+"]}],"outputs":[{"shape":[20],"values":["7+aWwZyc1kA/GhfBad/xwAZTr0CIcrdA7pUuwRvbPUBgNB/BSMBYv1zA8MCiJMvB5KSVwV6vIcFvrRrBzChgwatzyEAb6GbBgIAswWNjDsE="]}]}],"trainable_params":{"dense":{"weights":["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"],"bias":["fyb7vWwrrL7+KC6+ROiivhqLp76vxIC+/J2Zvvs6gb4rVGS+I0SyvuXenb4gvA6/sSntvrRhd76yv9K+P+YBv/n9r7yr13O+JvoVvpLh876tSsy+InkSvw6unr4d6pW+y5livsOhR77bRp2+Gx/RvTplmb5Ndyy+xpDovRkrhr5BBUW+h5SGvqoclr4QzLO+HPrrvvcipb4s0cu+GK71vsShuL4Qg3G+AXt6vmTmh76tQBS/DbQKv1y1576Q8oW+ZJ19vrKSzr7O4rS+dpuMvoLLqb7o75y+ROtNvqrvv76eAiK+B/WWvlfb9b1UWg6+diaLvj5vuL4ZThq+JdJQvg=="]},"dense_1":{"weights":["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"],"bias":["jGr6vi+N9z7MkwG/sOxUPntytz6ijU8+A07CPqqUpj6z0gC+h4oNPoCG/Lt8j28+w+fSvjJnZT2fZ1E+bHHjO5k+VD75cpY+P+yVPsjpvrxlV9G9AGjkuux2ST7ADoK8GWe1PtiJCT9SypM+EYfrPpcl7z2Uq08+xjXHPRJOBz1gxU+7NawMP39jVT6k7ak+mTtkvSBVhj0q7gc+jKd/PfWGh735WDg+WmruPswHLz4xyso+DY+wvjUR0T5wvdw+lk6BPms0yD7ubyI+0KqEPMoUoD7pVAQ/wUb4PjZqIj8sbTU+VlT7PuAWnLz8lJE+0JVEPgyYzr5hZ5c+Tg+TPaSM1T5LHpI+ULKXPgCwZrgtCeo9o3BTPnum6T4YpJQ+gGXfOynVvT23SqA+BEL5vmRuND5QBxs8Bp4lPgOfqb0MD4c+ivETPkiYxT6qk+89RcBdPlQlyD4R57Q+Sh0APuS5rL2zwRs+MqhgvUe9ez4Aur89AI+QuknVQD6sC3o+s+9IPu/qeD4L3+Q+FKJCPmrcLD5EUqa9lpu4Pig9bb22zWq+iElePmNk0z74xsI+StbrPuUujj6maA+/Qo+4PurJtj5hEpg+0tgRPtBy9Lus/B6/KftFPhbHBj58agI+o/jaPsQyLD4judQ+nF9vPmkSsT4lL7O+KhtGPvaC1j4="]},"dense_2":{"weights":["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"],"bias":["BAryvoeKxr28Xce9XDNOPqAJKr3hQSk+r03HPU/gdT2zypm+xaZBPMstl77eUHo+sxEWvncu0T0vrAe+LjVPvQwDVT6iBOC9Ft2OPYS4rLyigkm+Iy2cvvyrBr4usmE8URRfvi79mDwEgIM+28SEPQeFgb5ABBy8/fiFPDDJHb4vMju+y6Y/vR/CVD74GqY+tg3uvaVZND2NW1o+LumdvUlb0bwnWHu+ADuaPq1iML7gCAi+VIyTvqAQgr4bYag+ii6PvgU0lrxpJsk9+zkrPBS6HL56MZs+JuiFPVY4rb04uFA9nqAfPp59nr4tpOW9NdaYvL+7yb7trQe+++Lnvamsoj1+zpc9iychvjrMiL6ceFW+n5w5PpN/yb11kSo+0rGZPf1D77yrRJo+MY26vfVg7LwSTFy+XjVwvaaZVL7H1VS+w1tcPs2NPT5F2Yo7rTCNvj5OHb6wBpQ+L8cQvjA4qL6y9U89+BrnPHmTzj2yHBc9Eihgvmw21T0zuF6+RPDNPYE3qb21iOc9fRmaPgyafj2UgSK+WekjPg0CUL0vB5+9avu8PR96Jj5V6XM+H7kPPdN/ND75hDu+mjuKPlpBB73m6ZE91RYpvnAy7b2c1Nk8qdFNvqcYBT7fd969NLfdPU5DJb4W8Kg8vY3aPe1UzzzSKs87r+0mPrdnhz4="]},"dense_3":{"weights":["sbtxvVv0tL1BBcy9R8GYPYiDh70csug8GzohvgZiA77nfLg9S/epPaJOQz1EThC+uEkPvrnSR735rp0+4EQavsKWo74cNwa+/tr4vFuLBb7YXx0+dafBPMYjC71Pvha+qdR9vaFMJD0GJf+9EH8OvVSmxr3QASO8EntgPEQZTryhWCC9a4u5vV/D3D1ML8k++6HHPf2JID6Foyw9Lw5WPVdgxDyhYQA+PsdYvuzIA77nISK+0n9lvcOEvD0sK9g7Mgzjvq/4qL2tSwE8sygxPSQ7oj2t4iI8VHOnvREHyjynHlq+gt+VvnZE4j2olq29G8/wvDyOojwBMZS8a2jmvH3IrL14QCy+ydJauzSu/LwhIYq968kVvrPbHj4LWEG9Bn/Mva0XMb15ah0+qEtDvhjiYb1DE8+9se9gPeoe2Dx/EJM9acobvdfcw7wTdAe8KTGYvawTEL6/YAi+LM7QPQSDhTwX+HU8UpcsPARIzj1nBGI+lV48PXLY5Tpy4+E9YO0ePof+/rxp0yG7qMeTvWrCTT7UDPg8od5CvS5DAz0spI09nlfCPKkKjz6SsCS+yrFSvdy2yz0Ma7y9xstVPk3sEj0eI908QnkFPUf5ej2WxYU9RdwNvWYJUj4/z2I9c4lLvVQtGr+NjzG+NAgrPSqK6bzw8JY+rHnTvTKrx7yOi9K8RsoTPvvHlL3AjPk7RNMDvlCDo75qOo++4p6+Pvq3Ur0Bjim97ogjvrCEOr7/wcq858PvvRZng71SZfk8+r6xPYtD3LvVYzK+8B/Rva02ir4cxoG9q6wPulLpFr4EYEY91QCzvAaPeb0wKx+9pHJtPNu0o73P+e67SHA0PW2ZEj1nrSm9hSrQvWrTAj1d0oC9tFogPWO1S73B+wa+yhLEvf4gh721HZG9+k+DPUey5L0i9mg8gk0MvvodvT3fw5G9f84rPCRd/Trb3W28nNHTvQmbiD1Bkug81S+CPsrLMT4znR89HJeJPQAgGj2jyPM9PXCXu8m3/jyiwkE+","7ePcuTeB3TxQJxG+fN9cPuV+TL0m3Cm7bXb1vXD5JD4vvee8F0Q8PZS+ob1ByJU90pJFPrI9073eUSc+oGUHvZL4IL1xvbG90l2ovYzLRjvPi6q9b+wBPg1Bxj0gu8E9bp0Mvuavhz1kii69Gab8vHV9az2oPoA+kWQ2PXq5ZD13lRm8jmvevFoSlj38/oW+I6EPPEpmBr4Pq289yyO6ve24vr2IA0U+K/SZvUvu4D3BYG0+7us5PvSU5L0/V+K8VYBvvSpqE776Jko93D7LPSJF871sa927o4MqvfhjhL2j3hO9enmrvApnND1px5e+f2KSvZ6Rr703p1O+vnOqPQXobb0jPqU9MhIYvKcsOjw6Y0u9S1l5u06oZj1w1VS9IoJLPfn9wz1mqSq9UGqyPNUqn70+H1i9sHu3vbCtnL3dKr48J1cPvt3M8D3BwwM+OnaKPSt0hL0MIva8iCmdvD7J0b0QER49eJYUPV3Qjr25x1c9PY6SPQTZFLyB5Kg9fhgUvUdZvLyvHyW9Ri1BPuX5Jj4X1Ea8zAEevfO2Bj4FlZI9zvZiPZGS8j0zIYy9fNW2vVqqLryEJ/q9c9CuPYARWz0EBzu9sDegvTgVDL3W3ne9kAPYPSClVD1/BjK8kePvPO7t2734ExW9XSaMvDSyAb5N5eW9v3wHPFvnlr2PEXu9pMnBPUepIr1rEwe+C7ETPXbYo72yfA8+zLHxvZbhkL2FBG683DyFvXWCCL45rbU9SO4+vdE32j2i8d69xTmzPSaA1T18+AM+zCMEvUZn/L0ISIQ8N7TYPRJ93Txcdts7YyTVvaV3KT4Z/7W9PE6JOyCMi7vCn5O8X5QzvXzWBT2QvEO9/d6IPcPUbT4s+Ru+ruW1vQPKPb4ilYU+PmMFPv5lOD4pxya8RgOXPMxPID17OIc93BVvvqdUVz2heBY9JgRDvaxsCL6Mla+9RsC4vbZw4z3Y9a092KjYPEbBsTw65Ro97sMKPtjZc7w7n+m9hLGWO+Jlx74gLtQ9","KNIVPY/ybT3ykQe8llEVvXdpjr3mpUo9KzJEPYmZ0D2omN29im4jPWKH4DxS3Ai+35dpPD7M+b3rvD694F6NPf5Inrz/Sbm91wUxvsmh3L2VCou8fgLgPY7NZr1cdFs7Em2uvdCgf72byOa9OBaMvcODrD2SJ4897EAcPmEx4D3+fD29Pja1PVvVHr1I2GU9P13aPXeAD74jSV29nYW1vWjrKj40+MG8Lg/3PfprJz2y+3O8pKPIPA26uT0KOXU92MsKPvkRRDw2W+I8iKKtPbT0cz59WDE9Kp5zvVF7Xb1zy8m8jCncO298Qr13ZaU8cANHO6K1Gb38ya48DzUbPlv+Vz3IQ0s9idT0vMATwj3RFqY911QevkgwjbwJsF88Twr4vcuYerzvUo682LKXPRYzVL0vp249d/Wwvd3Fr729b6w9TYu1vZNf2r36oiq96H6OPMDtxD3cVAU+x8FBvSpgiL1SM0O8udUCvSC4qr2r1L28E6nNvTIlrzzEW9E9XbgXPZhzgL0YhJq87I+guCnCFD6vSx+9rnAIvfBZxD3EHCk+/fXFPS7CLT2EegO7NOQFPnwhuD2vNl89f7pfPSoLQz4DvDi8fAauPPJCkz0H5Ge9TYchPZ3Dyb1vqr29D3TXvWVUiL2vGXK8azA7PYGtSr0Awku8UcG0vFjJBj3bPWO9Qdq7vESGlL5D3i6+ho4zvdyskrziALa9hrevvYj7ML4jkXA9bxqmPUO3hL1DyNU9xoxdvvWAhz5RdVi9JMIEvuc4Cz6mUwg+4JCHvhg9Ab162EG9jyj8PaeYCL4WTro9tfimPINcMb4jSpS9klXFvZuMiz2P6Ky9q8OZPDdOiz0OeXU9LbCzvI4Wwz4uoSi+gtEdPWeD3TxWa4c8jTsYvt2NdD0vHxy+ZlvhvuZFRb2ZHLo9v63Wvd4GWLzsCV8+zVESvsC1Br7kumU9oKJIvaT+Vz1Py868S9eqPSt+tr3dtGG8AqNBvTa507yD1MY9cCVEvcQ/W77r1X6+","7AGqPZCiabwxUra9dzGMvLzMoz0QSKS9DsfxPEzADL4Wd6s9vzKHvcz0QL37SOu8b5sVvqQZs71AHpe+xCf+vBjCDb47u4g9NWIEvffZBr6YwkC7nx6QvTaFFb5BYd48v+8jvbL2Pb41Pca9mKyYvaQtwD7y6yo9XIPDPX6TbT3mF4g9cDIEviRJYj3LESS9hgXlPVrYt7xR1wm9LBWkPnPGEj58mVY+oYYPPoegLT2Iuta9VFygvUZWzb6FDQC95A1EvVmJbb2Ig2M8kVUrPSpSQb7jnBq/qJrlPVlDLjvqECu+iEy+vhvtQT6ZAxC8MxNEvYOOlj7zVOI9k7jCvsbqbDzDvwg9zRKYPGHjXr3JGRS+vyPGvn3oSD4ZxkM+zrIxvsDIvDv/CCO9umYRvjXX8L1n4827Vicfvd1irztbbSI8p+1JvUAo4rxC5r69LTIHvETEHLxUR4a94fNvvTkdFr6boyG+D7iNvWq7mL5SxPC8ifKpvW8hmD0i14O8zJEXO7MEsb1mx4M9xRtWvCBohj0B4wC+HPzGPMv4hjskQsW9Dss8vcd6Nj3vf7e8nOfTPF+fOruDNgw9HCtXPpuPvr0RQ0E8Li2VvUL8TL6xpBk+BiQ9PH2EB7zFgw++XUc2PmPMEr655xU8p5uIvYntMD45VQ++cMGrPfhExzzhwC++rZ5uPWzjq711+2E+MHWuPGvS7TzkLn2+Ji94vfkouLymXwk7yM2YPPKYv70Z/RM8c4HZvBA9pLwfYlG7rM4bPTJYyr0E2xK9MPGgvZuEmb0hJaW9xiVSvogJOz6oev094lAYvrPyiT0hsrC9QwtMPbj6HL6Xko69gXGAPXc0HT3aRQO8N7crOyAH1b1ePS++cp8rvh4FHz4KGq27KiayvDxQ4z2szqW9MnEBPZ7Prj0HAw89EeIrPIJGtjy5SmS9NqLpveWoLz35ztm8klA/vUCrWr1zSRy8HGbwumoRTb2DNiI9IfRAvqAIg725ebu9OxC1vgDP4z3Qfk+8","KyiKvThLFT0Bdhk+IjcjPfcIfD2Qucg9z3rcvRJ1eb3fsoo7X0ojPuXN4T2itMy833goPv/mMb1oxAA7s5pSPeIGs7x6bw49wFDlPWOxi755RwQ9+umrO4mxVr0Tjjq9lb50Pfe38b0lcye9ecq/PIlFoT0LlDy9cFm3PRSFSb0/mG68D3g9u6j/Fr1KgSy+WLijPZKaEj4/xaU9ruSsuitG/b1AopE+AxpGPWT9sj3vbjq70SvJPdprNr3SpKk9lf5XvmSjGb3zSt29iLTuPR9pLjzwMC0+mrmjPCH4z71IUVy9CBppvZANwzzYhsG9MelSvpGYIj4NbZg9eFDUvdQUJ72Jd829QUaXvJTlpL3vGsE804LdvXLs4r0ddv89At4DPYG+oT3q3jy8uS+mvXIFdL1at5E8DBvmvewaHbyYACo9lhT4vUI5sT2i7dc9WSZsPEXZnTvmHIs7h1EuvYLM/j2byB++U0qHPIODCT545xM8wPLovZXrEr7+Exw+S4AiuzQYJjwo1RE+9b1PPjXdJz2PLgs+1RvsPTt0ez0uaW+8Tmm9PR8o/T1HlAu+SGrIPCMWCb0qzLU9z3F7vRCdrz0dYmc8AqrTvOytbD2Bp0A9nmf0PeNBdb3peAm99lHZPa6O1ryT5zU+rNy6vUMPKL0zN6U+jJRpPjbJULxcpcM8L/SGPK2FNz2KNN09NsfavCK0LbxWgiQ+o3EVvc78RL59+TW9I0t8vBmtCT73WZ086P8MvNZPIL305zk+MCmBvTiGHb3JZNK9JDxdvrN/SL1Yk+M8bCasvUtDfr66nrg8sfn2vYgHzr3728a9bHhHPQhGobu2luG8kVG4O9uoi7zNzYo9Adspvgn1Vb6T/Ba9RSjrPU2YND1/09M8OuNsPokb6r3qFkk+VOkPvb3n+b2HIZA9Kc1YvIfuoz0Tnog9fWPIPaSSTb3ifZ87YKtzvmAAxb1hLyE+n/V+PmJupz2lLnc9onmTvF/vBTz5eiy8SwUcPbxoG72XfxI9","j/ShPPnSXbwQoBo+qQ8DvahMeD0nGCi+tyuVPTua1j3NZD49zf0KPBauQTwqYta6kkMhPNzMZj25/5A8uMnLvG+LGz4DYWg9BmwSPvZsnrxTmBY+wCEevdfurz2JWdw97RleO83ewj1yrqC94qS1u1/Cmj1lVRq+/vPfPDNSyb0wQc09mDhtvFxEJT1qeaq9NZq5vNP8rjzjGQK98JQ9vTv+ir2g2Os8/rVYPYhp2T1tHU+8XnlGPBXWmT0X0AU93F4MPjD/Oj7T93Y+nCYBviS1kr1jMq48Is2OPVY6dz6jVrS9jmm/PUfeXD3bSnE9J3cGvvRtKz6LGT+9TJKbveI7QL1jo2A9TXslPFETHb7FcpY95TOdPMQzTj48ptQ82MY3vhhXF735cRc+Rop/Pbrs+j3+owa9a0hHPSVVYzwDmLc6ni8HvqeHLD1w0Q0+6oz/vaBHWLzMY3w9Brh4vFl7oD0o5Hq8jXKivfmvJD6FR9I9DaQaPIBo6zzhM6s9E3BsvaEO6jwl9r49jMduOoejpb1hOVk91iJwvYmzAT57ABu+J0UkvmbmJD1sE2o+Qv6NvMEQ3T3utb09TwpGvcqSLT5oUek9RG6UPYuvpzyNp9u8I/hEPhkcrT1fq/y8pLakvNzXVz4xUdO8OmU/PrC0rbzyOpY9QBCHu/b9b76rhTA++vH8PRTP+jpbQW+9K2OOPHJS4Dx5UQm9OtvdvdZg4TuSF+Q9N9Y3PVHYOjxx6cM9fcHQPVkghT30OaC7EumTPNUxhTvBpJ+9THcEvvdUVT6Zcvk93LkNvkFlRz2Updc8hLCEPLR/Pzu9fAU++XmWPYJSBz6hnTC9ZDC5PeL8ST1UgdG8+O5UPWLcpr0zjBe+/oEHPWCbAb1t/g6+O/8ovDQABL3by/y9AB6KPejWFD2E6Xo9JCeMvcWoAL4dlk09YXk7vKwafL3yrOC7RLb4veZjzT05h649J3BvPfYIKb6OKpg9p8jJPCetAb2XyqQ9a/5PPht9dT6blDw9","Pu9VPguizL3dBRu9f+BNvXUMPD0mpeG9PPy7PWHOSbtKlAW+ZswxuztJQL7LLuE8e1DZPXypDz3wdEu7d4oFPq4iGL0h34Q9QtWNPDcpKr0UpwC9GdajPdzgwD2Is2s+7hb5PfIg0LyhiKY8VvilvQ7j/T0LJmO9Szm9PcPCOb3TTcU9h5VJu/CB/71Oszc+W/sfvjDrnD6kA4282ftGPFPMk7xo6Fs87YS+u3eqE71BSvE8qb+MvMNkIL7PJYY9/WuQPbb9RD53MNY6ZDqVvU2NZj2RFwK9cfQRPeIf7j39go498arHO6eEpD2rZoY9ndwhvb5hSz2iMzO8kjPyvem82D19Jl49ULQIPprW4ry8Goq+oX/rPYYSGz13VmG9xv/DvaXvxL1n1L29sEaLvemv+z3MaRQ9iVuIvL25/T3b0iq7MzNgPDT5Xr0UzyI+L3edvdy1Rr2iOA++fW1Fvm4rNbyIt1o9LZhJPs1D0jzc4M09w/9fvqHSjb2em4y8yFrJPdwcrjy3HZg9buK/PPf2qzxYOyo7b0sOPQyyuLuPe8w9X2bjvZMRHj1LMrA8QGVGPVwasD3mTZ89jOEXO9Xa5L112tC8FZXfPddNrr3asAu9U6O+PbSlCj4yLqc9Ud3BPYJ3tTzCzQg+4GZCvJrxRb1ESxE+a9xcvsOxLL1jUHC9Kp6OvdeffL0087E9xsAXvpuMpbxLXeW93373vZGMLT0Rgi++0ZHtPacLvb2WT1I9rTdRvfDrhD6r4r09Ly/LvWxDWr7ULdM++9LKvbtGtrzURB48nTOWPSC+dT2ZBTY87nmLvbxU270sE789zeePvUkkqT0ZddM93wrsPdmNrL06jH69t1Gmvd+llz5KDwC99fzfPUufxL0PUIA9ZJbvPa8+/TxvYaS9Q84Dvhs7Fz4Vd5292CbMPeDNKj2BkKc9KcgUvkAULr3sztU8bu6Ovtubej7oHri9aExevnh/cb40nxw+OJdqva/WI756Z1c+3Z3sPfkwar1cCK69","6d1pPSrceTyrDEA8BY6dvfaJB7qoQBe8Qq0LPZoeJj4s9uk9mYDFPazjw736GjI9jeiuPQoUw71E6t69HZIIPsJJUz2qHb892vHDvRmOpbx7u1G+Vc0FPA54DL7q1xy+g2iOPb/JYr2HnBG+sGaKPYn8mT4N7BK+cxAHvXFOozyJHKA9vVQrvtWIuD0zQis9ZhM6Pm9G+D1yFUu+2NsFPnFGxj0cgD8+3sCRPbjFrDyaqhG+ginHvbgIxr3CZpg7WrN0PbVOHL2uie+7DN+tvvxU3z1YyF0+cDbNOzp96b2UEE27z9V4vjKN8D3ASQI+0uB7vcHAtD0syMm49JK/PY8YdT3Qziu+zQbTvIRrPr2hAaC9m0cJvqMcrz3LKY89UwkCvqyAyz2idcw9//cdvnmAfz5wlxc9EZt2uxrX4T0rfb69yDSiPTElmz3V8BO93r/EPffh7r0vppA9SS4JPfriST4fjVk9ZlpFPXhxQT7LLLS94XkIvaOMJb1rjQA8UNEWPaJDHL6/QIQ9s3udvRZ2WD38X+Q9eyLfvekbILy8wZ89aTg+vtShRT1Xum888jNTvZ6oabt/nBK+ZmWhPZuVUz6xPR++YFUUvhUUYb0/Fvo7ilrBvX+Wtrxiqra92wg6vtYchT04Kqq9gjP/vRLukL16UQK+81eSPdeLuDyFp3Q+NJYYvT7qAD7Zj6g9wSpTO6Nbh74kToM9kEOMvQD/1b2pmoY9tYU2PWexPr7Ba6g99D3WPXZUyj127cu8pIZ/PZJzFT6XTEA9bpKxPSTBW779t+K84MwTvIBaLL4i7bs9B3xcPc0/9j2cdTc+MbLPvZbi/z3mPc08xRG9vZNtXT6ibBq82CaKvd1mB71h/jQ9IAuavCsG+T3gfqQ9qfVaPSJLDDx52DO9kN8SPu47Gj4O+Cm9m5qaPWwLXb0xmfC99g8vvl5Ytj3RjMs9oJTiu/7nLz2X80Q+60LKPekMmT39Rg49iV3dvfqP770J1bs9Zts2PkWwrr00PiU9","+sQ5vtAYGb1FjMg9aIHLPOJldD2KkGE9yw6VvOu8Lz4rbzK9/OB0PscOrr3LYI+9nLUMPgYVVL1suL09IdovPbJYzj20IWo9LhC+vEf8g72vXxU9KeReuzyqjr3L/xI6VUy7vVTl3714Du89S2I3PKDAFz7qhuM9DMEkvoLEGz4EZqW9B1d6vTJbjT3G7cu9loBQPuGolL3cZIE9dEPNvZnKnTwsrmC9ZmO0PaH7DD76CqA92Ws2PVL8/Lu3OxE9B7aMvaDOkT2LV4s9TqtiveAqpL6LncK8g1E1PSa13T1cP7A9ttPfvbw1lzzdVy2+eOm/veARJr4+yYS9HUnLPZM3Fb133gy+GtN1PD7n+7tOywY9A/YruyzSur0jTcm9O0uMven6iT1HaYC9L/ufvdmUfjwwB7692jE8PJpyHj04jOK8clelvdHx2Lxam+Y7DBamvTR1hLz75QE+anDbuzIVXb0ZsLm9QjhLPSjF+LyLTDq+H6UovTRfIz3KcYu9d1YRvnfMqL3GmNW9ooa7vao/7LwyQGW9NeQjviX+D74Bsdg9F1NfPYm4ebxjCRo9VsWTPIzqdr0uFm++trFtuyuJ/bxispo9kqgHvug8Vb5ROGa9gFCcPR6Dzr13B1A9A8XVPadDgD17h6a8ORyLPH3mIT2M4328ENmePGFCj7zs6yO+0yrIvUkVRLpPQPM9cDAePck5Az4UQw2+fMgCPqXarz2UngC+1gZUvGrhWr16eVa8GJA2PP8uwrz8hG6+1TkbPcCw2TuGheY8SONTvqDDRL2nEqG+7rCiPBiItT0pHye8tAfmvUUEMD3IL729r87ePIhRtr3KNqW8rrkYvJYBIz1vSKG9sZfzO/e3nL0DRXC+dA8UPvLywL3UffM9nz15vkoVQb14+b08elo1vWLJ7b0KMjE+tr/iPOHQuT1IgbI94x4KvSnxdL0rShq9YXwAvgxYtL05Lw68DS+jPXWBmTzjuDe9M6aTvVOtgr1TEvQ8RWrrPWDeB73DiKk8","DRKIPXSxhL2o/Ky7G6oBvUPWOL1NaAc+V61wvRakQj3Ne3a++n8APT7e8T22b1O95b3UOixMIz7UYYs99CUHPmAfW7tGWew8pWD0vUVCk70PCNk9GG9LPWu48bzJCd29N8VTvDj9nb2HrVM9GtYpPjJHWL0MFEu7eilbPUOI1L3WE+M9BSRAPNcHnb3u05W9zh4GPlg9E72HiGg88ZiLvZ97kr7q5wW+11mlvQNaVj5RVA++DQtNPdWtJr1bHJo7xvW6O//aCz795pK+4aWovejtAz48nq08HvzpvCiSgD6Dm4W9U0b/Pdihajyl3gu92P4rPVf1n7xIVAi+Du4HPYBTHD6afQ4+TatiPXoFBb0Rigo8GvSKPZYTWTtFTfq8peZaPSNY1L1gJRo+RHTjvZ4Vcr0FQ9a8zx/iO3re0D2tdBA9TWSXPU0eGD4ltpg9c9hvPZOH47wN6D29fXCJvK42RD0Bmuc9BBm8PCWFqbztuic8sazPPeDqIb3TCoE8wV21vXBrHz0hoS8+/YD1vVFaQ71Iiii+Wd7QPB+qpD35JLe90oTavSGyVj1mw/+8ieHUPcJwQT4vZZe9zN0fPZaTDT46+VK8exg4vV8nLT2QFYk+wZIXvqXw7z3x/um8XawgvmFvizXswu09zdttveBCTD54tqq98K07vTD5P72oAI28vJG8Pd21ljxcn7I9+Sz2OyxB8byVh4c85NywPQEq0jzJBhe+PNhgvAtQ/zyUH7480NGFPX/TXL23P9k9K/i4vQ8ne769Rg290kqfvARkHz5gqDe9BhPhPIna1r1VkzW7b8I3vUN+wDvY8fU8v3EWPqRNID2U1X+9ui4Cvep9OT5xJIE+WMvSvOTgVL4kMEc9cQG7PfxKWT7A2vU9+7zKPInEvr1gwaE8JfrAPSbWcz2mDX68PWZBvR4pwL3AdYC9mwEmvrmyAT4bvpi9dZZHPewIdT1erJ29IPHfO0dzmT0pJLw9/hOevQGwVbsEEuU90qtiPcgSAT6+U569","DnBOvt+EAz0TTuC9zWK0PWispL12OFm91U51vueEkr1ip0E+XpZTvRBvKD3T8rY9RSFhPlUftT1p7RS97WtbPZLLbz3aPKy91AgFPaiGPzwg2we++AHrvUcCmTwafm49dyDZPZyXGL56Jg+9C7+YvfpFNb1/BvS91jpRvFwmAj5tOSC9BstUvRhzEb2inyI+kw4DPuCDPL5uhOo97b+qvL69pD1N0mw9w6SuPElRcD3Avxy9vqa4veqA0jyixzq+KBn8vdoDf72axY89oq2lvVkv1r1KiKO8EwqlvVvhnj3JWGe7oY6wvRmhMj3TilQ8Ju+cvA92YT1j37+9jReyvZOCDz3YLts9H/rIvLqtjD3yLxW+bffQPT0aXz2g21+9Mod4vbLdkL2ojpM9s2pcPmjUIj5nzsc9nJqaO9yYgL2k7wy+yiMJuzU48byB9gq+8EQSPWWGIz3hjAW+ocZ0u75pBD3Bx4K98G4CvU5y8rwW8XU9p4+4vPopQr1/NE89I7/OvbuDXTw30Aw+Ulnbvf8z97zgwpu9nQ+bvceelb03kw29H8E8vZh82z3K0Vs+VPI2Pj97iT3JICE93J0PPPW79r2yP5o9JdJtPB82lj37BRw8zO68PduFnr2f6fa8veYevlCXhryt7i49dhbxvFAtn73ezDe8+Qh+vSGhpT0Nkb86pXNdPgmqLT5epqi9Ul0VPix1Hz2yJJM9WdMuPWiChL7iKxq9dCttPRaHxLxQcbq9ug8QvQI6DT3TcF6+b0vDvb0eXL6I8Pu9Q/j7O6VI+T0hfSy+kcWUPWf5zL0e57O9sFj2vR0ay7y4GiU9mtERvl3xAL5uWxS8vkoYvkpy573pGkC9tUDOPVDiWjwkjZK+FvUWPIYSu7yjKbs9dYm6vSFH170T0Y491kWLvqOVZb3kAo69YkVVPp5hor1yKje+2zr5vcLwG70p7209hfCFvSWCzb24VDy9JW8BPtorgj6diOM9XZBYvuqCpL5RSVE9ThR2vbGEibzEE7S9","Q2aHveY7Aj7Cuby8FwozPQlFLD0RHzu9ZfF9vJZnLb3je5M9CYUIPlxohT3UB3M7RHYXvM9FrD4rSwY+LR6MPuHS0Tx/6cs9i9wwvO81hbype4w9NpgNPgAKWb5SDjA9pqtGPjpgar4091C+fTQQvmq7BD3zFiA95JEhPb6nEL4h1NO9tkZ2vLkx5b07QZe+bhisPXYalT7+yE49WbgZvTpJZr3r5yo8ifWHPEvwqL0BUQO8JxrwPIQJLD40mXk93Q1IPladBr4crB6+zj+ZverzxDxbh069s3/XvK1HhL4GLX2+ibVLPZF7hL4oncM9Zw8jvrQKyzx1/L2+3f1OvUHwkbw/Kp88fRU0PqGkBL4knQe936EDvjzsg71UYUO+3VzCPPQowjz9zh8+5tI5vXtcRz6JsEy8dtyRPQYALz4tmWg+qx5zPUZbnT0MsCG+js1Ivu6Ld75dhSW+1/Q9PV6SMr2Rnwa9WhctPXzudr0Z3N89aRH4vWocY72UfYo9kBkdvVt6hL02pGi+81+YvfQx9Tz6Bfg8i1HzvSDoozwX1ua9iCAKPQvg6T0S1sA7oQuavQCVFD7rxxq9uOUjPk+uTr6BKV6+KEtTveks7bwT52k+XYQbvrzTBj5BIx+9U4wrvmKB0DxOl+G8JrMMvnxSQz6tOjs72vCbvZttiTw/ncq96wFAPn1Zpb3omS89mFyYvszESD5Sd/y9GyspvqBz3zyP4Uq9orypPTV9o77yIoC+uwMSvuHHBL6IOYG8KtqrPA1HBT8Z2MA97ZcVPoLmDT2eOZK9MucIvudJar56NJS9rSmHPegp8rtvCRM8mH1HPk17I72Fog093XXtOzeNFb2yCVU9fUl0vE42NL6oIVG+L554vAKiVb3yL0U9lgOePmgDfbxoZI88+YCqvVlIqbycXs09kZjavRNdOL32l/A9B3wwPtRw0z1Wewk+GVsBPrNEZb1wcrw91EglPWFMgT4FvNA83j8ZvVLNMr2e1ck98FsfPYBBh76kC1i9","bcvFvcaUEb2mbd69VrplPZkiBz6AsyM9x61xPW1LNLyVkQO9VhbvvYmY3b0Mg7y8IYqMPW+rzT2okA099VKJvYelFLvsb/c8tTLDvanNBz2OqRI+/KMLPiuT2r0h0D88dJDQvQEqoLxihda8a2stvenC4TznF6i9osWEvTr5Nr1512G9w6aOPbPO773G0p09ZqMjvTTnDL1BYP889y3gPer1nz0xsoq+TAc2PQBEKL2zusy8uw7YvYVosbs3YZm9uJk4vjc0H7wZi4+9Y3yjPSIOhL6EJrK7nzn0vWdUDD7BHae9vN1GPIweEjzPUgE+baGPPf6VpL19PLo9D6ZQPmpsxT1Nup28GEQ7vXs+Aj2oiWA9I5X3PBdNDD0k9Qo9gkrMO6o7Lj2n3Z89QKQLPFy5rzyOsBU+mbFnvJfBvDtwc0e9IlN3vOOFhr1Bwie925QGPjr6jr04Vxk+laA9PTNSNT3nGwe9ti/YPeGkfDxJjms9vXwEPjqlyb0pUz+9L1jxvPM7672ljoS9d2CPO/Ctizzm73w7ZoyAvYU+hb2M3Oo817S4vQp1Db3bLFg9hXXbvcwQ8j0F6AS+7FAwPfk8ZbwD6sC9adEYvcByQTzDeSc+7KLJPR9ebj1LHva7w/oRPSgDhz3HTz8+bgDvu1laxj0Mwuk8UkCqPZ1m3b2ZDIO9Pc39u6g3cDxTlRk9/vPEvXGI3DxULTE+ulXGPbw0jDxwr5Q9wrvXvdIfjT17S8C9BaqNPKjb0L1sFsa6nDuaOn/3lT18y449s3M5PTyfTD1Vjqs6P0wQveznEr5co028Do1Mvb1VYb2rYow9+maUvblpVT1AGgC9FAWrPbzqET2oKYS74UULO81bTz1TfAO+sOJUPVj1/jxWrW+8HwUHPS/3hr3X1JQ9mkNZPb9YQj3FT769SirJvfBC2bwgtAW+ooAaPm92R7zrkF89PHREPJkiNzxn2ZQ7UwDfPatBijvGi5A8bO8svPgV97tqnhG7plBgvQeblrxrBvk7","eXp1vffusz3EDAi9SEcCO2jt0Twf5SY8s8YvPTvUzrs4lqQ6AIpbvTeeCb0JO2u9dTRyvTSekbz3PWE9F3fFPbccOrtkMaE8jkW4PeuGwD2Dq709db+yvKUZvDyquCC+I2SoPVitDT2onoG9igLKvWJ4Qz3TfjI931mpPQTHUr1Cr6E6tkcqPQKezj0bQ4Y9HXgQviciJj05uow9qR62vdw1Ar23k4S9oyGQOyn2K74gVze9uCLfvZpE8L0YrJ89aIsjvaq7BzznsRm+WPcDuxAaC73yAKk8Y9IkvZ4ocb1YW787EgCkvQZPDz6jZz+9AJXXvTCA6DwFsK440MaivZr6Dj4bLqs986yOvBYxgz1NjXi+BUa3vS+Quj2SLny77umtvND16jw9EgI+tBxgvR80WD2Y9hk9gqyvvfvL+73EpYO+5bM7vRCtiD1J1F+9/3MOO4TB7D0+qFI95YGXPemchD3alp69RLSuvVG5hz2z+Lm9YjsNPPS1/z1xuD0+t+aMPUyKrD3wWdo9qfH3vK19Mb6b4zS+7K0HPBIhhLxRAxq+cfkzPh3N07wGzaW8ucX7Pdsl57zplMW95R2EPOcEjj5fPbk9Gp/rvMcOS73SHVa+3z1wvZBObb2nAYc931q+vTbtXb51uAo+YqgGPtVgaD0svFI+D2t1PaqJmr5c1Ai9SF2kvSt8oT1+qT89MRyLPY/ZIz5SK4W9SDuJPiYJpz0VzB+9GyP0PRsSoT2QAY28PxwcO7l3lb0itd087jzWvVyu5T1gmKE96yRyPqaaqj0rCw2++y4BPZ0axr25WeA9DRwMvmae5D36Uwy9y7mWu0ovpj1ls+A9aV5DvcGmTD5sW+A7fwUlPa5ZD76XmCU+O6GePVzN071rqiq+OAG0PGkvk73GfwE+F2I1Pas0DD3VjR295ZRNPo5Df73rQxe+tag4vnhCTj6Xw58+dUD2vJAFUb7MYZ696l73vGlUMj1wHLS9OtoAvYnztbyA3828YVPbPWXqir1wM8S5","2UWXPl/9g7kV2/A5IcghPtXi0DxQ3ui94umbOn/LcD0mnqq+yhrSPbDDxD3zjli+glXLvT1d67y2AwA9VLumPToVnj1siHu+W8hePphQRT6XFRW+8PiUPX0LKb1YLpC8bkz8vVUKTr1yG6o9aytavpEj5jxI4hE+QIx0PRbkAT1ZWDW9W/QLPpCtU7xtbBG9pmJXPgFbQ72VKUc+8eA2vaVXvT1LYJA+sXmpvZ/FAb5LMyM+amzBvXjMuDwfWX+8O1DSvdX/vb2Wwkc+WcGJvWuwxb3BG648IlM7PQCXdb0z4Cc9JG2ZvVGFijyZqD29OCPuvYvyOL4fEKw59gk3vltZE77DU789c24KvYEQWbzkQL4+3kNNvk4P3L2j19I9SG40vB/aNzz/Fcw9jG0QvT1hVT6mcSK+8sxyPriMyL2hQD09yGSMvsp9IL3XuhA+s2YePSt9NT5NkPi8p791voQA7D2qsYc972rcPWJEsb6gyiI98uk0Pkj5bzxEDn08Z2icPFeRGT1CHbo9Mh/2PTTaqjwWsZA8vkjhvXRnjb1fcy890/SjvVn/Lj0X1XC+TZ8mPeqKib3R2vK8dcOEOntNBL6cc+q9OgkVPla6NDyh7nw8lEgpPTY/Kr0586o9V721vT6Krz0rdRw9RhPdvCyw4TwLb/i8SaN2PvCoAr0Y+6c8HMQWvgxbGL1iVh0+UM6Yvd88NL2MuTk+HZ7VPd+gO747WSA+38SfPZ6rBr10NfS9f7KwvVXeWj1Q+/U9MlHzvOY9Qb17jOe9vdqSPdjynTwLNyK+wF0lvdEn8byHtkQ9RAIVvQaIKb3OoWS6PEUXPbv+ij2+CA0+1f+MPfpVLrzFMEu9/hPWvYRnqr31bz097ZyYu4lJwzv60gi9dbNMvZmr4b64Cge8cUGJvOSMiDy6EhM+THBHvIviFbrY+Xc9KMbcvch9Jz5ph6u8Iwm7PdvIrT0yyq68a2qVPPgDrT1YNF095+/kveCQob6F5d+9m7XevI0T972VXY0+","I3GQvZ8uBL2IOUW8q/CtPX/lIjzcdgE+MEDZvULhlb1qaDa8lpJEPYEojL1/8dM72pwpPQLBu7zftRU8tDpCvdp7WD2WIBm+kn++vGjpyb18e6g8fOCxPYfDZDwy14g9eDEBvmuDFz0DD2y9a2uWPB8rEjsslym8eBxHvaksEj5vpr67Wf2HPXZ0nb2Sg1m8M+v0vXZoujyOVA69xAalveSuMr2fWhg+aHvQvcNkqD3H+WW9K81AvClZdrzghFQ9Liq6vWLW/T0mnr09QjcHvm+XG74Xxw06E641vQCFlT0Djww+RypMvt+jpb2nYGe71qCkPXpeI759Tt29LxDNPdhCoj2GEh09oQCRvckVsj1iBBM+v6PgPDeCnD0ldYu+B8gdPmxMurwCUbo7mA76O59aTL2evHm+Hmz/vbGfOj3hBuG9zjSnPZjjm73J2DM9fcMlPb3wgL2IueS93QZQPmu+NLyuWRW7zf0SvjvxRTt3D7q9+UvEO4rzUT0yzoG8FH1nPdC7wr0M1o49LdDUPQ0zeb3ltrs91y43vEuFeT3nBly+4Bcsvf41Ib0fZmm8kaBKPcZCs70JAvu9B1mzumQaCr3aSy0+Xwovva1sqb3AJNq9PZRJvHAgvT3oiOC8gL8CPIRMBb2stCM90ptRuX2O9zyRQbQ9Nk3OPFfHtj3b6+M7RgouPaIw1z3TeZG9wFG/vT2VYLwx3hU8nhq+OwV4jr0YgYY91ZSjvbbPGz0xOE4+uSCvvVnNt7y9vXW8YZaxvH3eC74xZce8ue9tvtAK9rzQjYM8qA4OPr4uJ75dYOo9m7hzOlY15j1YYgQ+CNvHvccbr74I1vY76R4PvTYY0T2OSTU9d8fTvajn5jzCKLW91j4fPT9R6DgkkFa8F8U+vk3um73dIjq9UUgKPqyl8rxvoQ++uaUjPTxuwjvSPBO+Mn0aPTtC3L3qdT8+fq4PPijoGb1u74C9UhNJPrQ3jL4xp5E9UNabPRP5DL1pfSE+pQQ0vUl+3z2hkmi8","fqcCPsuwC72nEYO9MgYVvpgT6bzi3Vk772IAvH+SNz0pHB6+Yu8avBF66r0/KWi9/j6xvHa9Dz0IAjO96e0MvSJiITt8PiO+tsqJvDEeCT2GCIS9qAZQPT/rB71kZl08p4gGPZRpmjxf9QK9QKuVud0+qb2/XLi9d8Unvvt2373xfZg96IYJPThtyr3uOSK8MeQSvgi/473tlA48Fz3ivZ7dED1Ev0s8wXuUvsvQOD3+5La9xMSfvcBpl71vQ0a91ciRvfiUP7wSqVs758YbvSENTb4HLDq90G6lPcHFErxrfne9vCm2vYGdibzhysk90uSSvaJLUD0Ox7u8lacGPrT2hj2QwSo90tUCvSTMw71L/a68vUXIva1X+r3Nbva9S3DevDC9D70AHe28iD7hvNUR6b3GIpI93qzMPPEn7zvzB607r5WSPdul7DzFWYk8w0DIPeBXkr0jWba8INWtvVTYpL2FZDc9AIGZvFVztDyQ9di9CxsuvjbNCrw0OTu9bPanPcr2Sz1VN4u9RxImvvgBK72pVIq7H1IDvj1Job2PUR099osovhbyPj1aTna90HkHvXqjBLpaNXK9muoLPEjztLxWdG29DHJrvjlfqb3nWAG+S2x6vcb4DT1yc3a9fTkwvRXNkT3rR1w94o+gPAbd/bzTYnc+T4USvmtLMb3LHME90a4DPX88m7qcnTu8bMQjvl1pljl3sNw8YcLcu539iL0CqAW9Sw4MvvMNgztQx/g8IT4cveEKBj6znQM923qWvD5aHz2Rop28Ib/vvTtgKb1wPx09tNsRPalVH74D46W9aRKYvTZynDz8nou90pK+vIYicr15ISi9eWGLu3lvFD2NmF+8xiTgPJa9oD0Aol48MXPNPSLroTyT7wa+RNoHPuKKHL1UTzk9pC8tuwVkdbwJCWI8WrLUvbbB7Lzm4Si96Em7vPP9jbrKE8U8OMShvQGvJT1RJu08gdVvvazB3zvI42C8SDaAvfeCRLyQAPS8YqaovRR/FL3/kvc9","UMOrPcGW0Lx7PuU8xh8BvcWEeLxqwr29bl+sPfxL4rxAVMg8yvPhPEfVa7x50RM9j8BtvV1cFD17aLU7QPe0veRLHj2mXam9vEfoPTHUfLvFRXA7812ZvQ70oj0CPsm9akR6PZmrDz1GhG49y+/EvatZV71X9HC771M4vGJ3g73C1R08qscsPd/tLLyUKmA9f6Vbvcske7xtJpi9WpqQvcHd0Dw4eLo7QUlGvXnPQr7p2BC9dEszPRgfF72cxbe7Jy7hvCLLtT1ErqE9oLFbPUAQ0r1iB6W9W+EFvSGjKb0bUku99bB7vNpC5b02Zn69ejJuPdWPML02kL688WySPLAMUz3n1Qa+GkIePeB8Tj11tJs9tFKGvKIvwr2kazo9RsYPvgIFyTzU/WU9tJjePbZiDT7pkL29ELauvT3fpL0KAJy8aEwkvppqZD6N9Gi+RmRmvU2uiz1CdyU9RIkfvKZoVbutKh0+pWAAvukGMD2qwmW9qmmOPZVFXL2wN4u8zdK8PR7y9zywe5w9LEU3vcefDr4s36m9YcpZvXa2/LziYxi+elJfPZKxWDzBXkY8vqVMPpmGkD07JU6+tNZDvIY7AT4EHqk9YABLvjP97Tzsmoy9yA6gPIG/3DthG/+8a8AUPmk54TzdWus8YtLpvHU6Pj7OaoK+FMIGPVE4H76ukUA+YPYoPuSZcLxRMMA9w9vIPbK2AD79Vq88+pdCPRg4CL6+gSK+lZ4gPRa3Pb2MUA+7hIM/vnhNiz3i6la9BdVCvZw5xD33is49PLmcPcHKlT1Y4jq9xyoMvvo6H7xMGUi+Po8mPfrYBj5CHvk9aFfyOnh6Mr3fpp27trONPDq6M72LG0O8zaYPPsY0cb2sq1y9WTcePQNGT75f5Cg+tgkZvWucjD3W4JA+pfkNPfq0JD4hbAG+pvLYvMI6k71SId27trxnPYkqSb5NU5w9A0eGve/dSr6BM5g90apFvecpvjzHcSe+G63gux/5w70LPaO98Cu3u2dqiT7s0UQ+","vMT8PHTqo71hq8i9X5YEPZqcCD4lSyQ8HIqqvVlYNT4Tpr69E/ayvX84yb20eDo81894vjablL3yjD29nUBXvpyPvL1OZkw+7PSNunfI5z2I7vE9ci9JO2BA5by/bXO96nqJvVwP2L2pHC2+1Jw4vuXTEr6p56O9MagePgj+rT2of8a9KYtVvQpu+DzOpeg8jUepPNeepLz59Vk9IDa9PLbsBj467pG9NIKhO1gWtLsxSvI8AyC7vXntr7zk6NA91ngKPaKktz1yXI09a7YpOh6lxr3gLK29IjHZuqPmsL3HAq897ahBvqalmbzGOIY9gl+QvfhnQLyKGmm94tLWvQvLib4n5Bu+i9K8vUzDAr0tIA8+GQoOvBxMc71nmL29zt7jvZVUZL3UOKC9WDqUPSVtMz1VSns8z7i4vWaUxr2e6Sm9xW8UPJ/AhL0SzVI8pFtlPHv6kT0uTe69ex+cPaetg7042TS+fDy5uuClHb7AGb69JGf6PXbp7DwUfuo94a0ePtsj4jvpCIY6Kha9PWHtOT2ZO1i8VPnyPA4dgj1ADha8c2LuPQP5Aj3eERy+pV4DPIsyrjy/sT695L/BvclGeb1sg2u9qfuPvH95Mz5Nvbc9IfbXvZLDxr465o++Eah2vpiVMD3OdKO9LiOpvOkekj1HRxq+ic9SveJm6r0tDy8+6nwBvh1RtDwCqta9kaduPTUSKr0AdR4+tdavPHchE76/CjE8BdK6Pa+1tLudrlO9xx7Fvf/nA73fS9U9rsWwPP9XGj3VPIg8NtgYvlt8OTyudWE+HNoQvkVA1j2lzyw9kUWOPu2BtT1fVQS8IgImvpo2VTzk31o98ZPCvKJ4QT7oTpG8gM0UPt/wxj0PKWy9fVuHvQXJNL7CWTs8usOQvdptILroIh+89HBxPRg4zL2EUXg99LX9PefitL29i6m9UvaTvJJGRz1opPe8kbmKPixTaT2jGdE9I2FYPntXL71CfTg9NYeFPQ+CrL61vYG9OoqxvQ0O5D0XAyS+","4DAfPHeT6730xY29W/BevfKdnbxJsiS9RZo3PhdHAL7018i8wB0KvpEH/TuZ1Rk9i4itPb70Sr3DNAK+CTuMPRUsKj5QKms+JtYoO1ZPWjybFxG9NrZ8vtOEDb7g34m9gnVvvLu4V70pBdW9oJ5xvVNVjz1UvxQ9w63Pvd2QQD7VFGi+XaESPtbkdD1tK1q+d1ztvC/yoj7oMr89msilvdELGT0wthy9FLUgvt3Jt73DPfI784y9PW2Usb2xJLK9zR+Ku2y1rz195YU+osmkPiVqBj4xXxA+rEI7PM7bMjxoKFc7UOr6PUsRLD45qpa9kUQjvWmY8D2I66O73k4ovpvAmjvM+6e8B/uWveowrjwa3vq9xqdgPiGuhzzzFKS9d2lbPVPHeb7X8yq+RFvSveraMz26Aca9Wok0vcr9AD3Kxl09+sPjPGBxsj2DsZU90TsgPqapYr2+Pwk8IuYjvaAiaD0PnFE8YEYtvmIo772YNg08OOcuPR9glj0W/F28i/nNvd/bEb7lJC69gmktvfZ5eT5Lxbq9VgEsvR3J6jxfFa09nfLHvWh6zL3z3Ug9bwYWO3j6uzsmnpu8Uq88PfnYTD40SnY+nCbfvCalkL6RpdA92RmFPmOR+DwWf5A9jkPKPNwg/L2tkQI9LTBlvaGzJz2A4EO++QULPe2ge70OKoe9OVZvvZwpiT25oD89IaYQuy+7Gz3SMD69oy2Kvjo/YD6ovR+9PNewvZ20ezyhvFC9TAyTviW6Jr4WKL89dxBbvBPrVL6RqaC9whKGPRGguj2iOae9BlGRPch1Dj5MKxY9upk6Pfz9uz15rPs9ydRbPImhdT2yKeC9xWVPPQVmc75rpIe9p8Cvvf7vxz1nShk8/6KPvLlUkL0yzVU9SUkQvkfPbT3tRCI9ZhXAPM3xNb1T5z6+GsMQPPaLIL1cROU7R8xgvivY5r09TS0+gvqHPGzJIr7XLwY+s1vXPQEh0ru5w0K7pzIePWsZij1C7I4+dHUfPnYwJL6yQeG9","va5XPth6JDspEbO9rAQ6vfreobxYqZG9qljJPQ5pEz643DA9reqLPTSdA75pXPa9PwUGPkBZt7xhw4Y8INB3u2TWu7xjH7+8gZoKvZwFkT6n27g9t5yDPAFlHzrQJaw9v9BrvGFryr0SwAW9zLwBPtn7yrwuhJ28vnAPPZumhT1mwgK8tnC3vU/zxj3gN4u8NVXdPYaJ4jxyqYg9S5UWvdC/IL7wKcC9U6IlPdDPuzxAe9i9NYG6ve9WHj4UGnM9WeiBvSAbAj7UgC+96m6XPWwLMb7eNdI9nhT9PdQsyz1PKKy9ZugUvXQa5TwS9J4+s4vCvfPfej2Osao9HCARPhpUrTrY86O9ne0qPU7P0D2Q6YS8aWE7PRZkLD1bXYm9HwuZPUAS3rxKNpS94hEnu/9xk7wZjoU9k+6bPe39J73Osm49Td0eui8L9Tsmr7y9er04PP2/5DyZj5G9GizHvR//az0zLQE6b+r/u5SG/Tx1rhm8q5JPPFrX8z1/ElM+WJ2BvOZXsr0C0fc8bI8iPQMakTwYArs9bjGuvPNaTT089I68Tk0XvmogPL7rClg+OtbVvVeUZ7x6iyi9aYEpPScfCz0V1KI83XHyvWLMRT5PBrC9WnNKvkpcmjzL3x49bPMoPtbFwD2gpAq98F+LvQavpD0+z6Y8s7ECPfVo/b0MbJk+wG7fvTn4Nr3mEh0+tAOGvHipH74bzhg+J58avomNOr0uuF89AgCnvTr30byT3yI+Kx1NO2ZlgjxZ+1Y+L2cUvTmLRz2LWtk82q/ZPbfOND5VPKU9aN3GPWe0PD7VkzW+R2y4uvaRgDxJG809GVtqvMMcI73wzX69ZPZevaZPjj1w9YU9LeULvcu2tzyfZ5I993SKvHxCZL1MDu+81WfaPIPC5T0o5Jm8u142PMuk7j3bpgY9P0/5vX+mmj1lQ2U9sGfZPfinMzpgsAw+XA8iPs36cD0uAZq84dFwvWORpj1DKHE9DLXXPEuGIzt3i9A9G0qBu7oQHDxioLu9","smmxvA7k0j3h4/K8lPs9PXkeez7kaX69Jc96PQ8Lib0AUFY9h2gjvKAcBz4NvuM9oYYDPsUsFb5/fFC8dI0iPfrw+bxduJK9FmfmPDMPBD6D4SC+0avYvVzXlj2Svqo8gx8MPp1izjwePb67jo0cPTtJgj28bjE8UG5zPGgb1D3MMcG8w38iPY7wJL0jPAE+OXunvV4bIbsYGgo9o0nSPX0kyD1RJJc9pPbNve4pab6ovfK9Ta1/vKiSBj770Ku8vUMUvVGkGz1UceU9bBqlvHJ/ND6ieyo939+7PEwzjTur+u098nEyvvsCkrzOARg+BImOvZcPvzxCfaY9zZ4KvF8FPD0jLJg90w09PeTiGb3CBse8tNnLvLG6vj0rL6w8/WyvPavbor3zyAe6wxUPO21x8z2ITFo927ulvUMY6L2L6UC9k4AhvLnbRD48QzU+SKBnvakJ0T3NvUS9zKz2vPp72L334iQ9lyYSvdqJ4T0c1849sH4vPusuhT2Cjlm9RnqVPZNCj71/x5+9yaNevvydk707t628Ri4QPoUiwT3OXzY9HuVJPvYE1z3zaTc9cM81vjjlG71ij/E9Ph4pPokfSr798bA7DBXZO9x3+L1BIba9+lphPQMxNTsoCzs9PwIzPiTuCT4Trh+9abo/vQG/Bb649A+9fRMmvVUwob5aXTC+305svQAduL2QfPO9x1krvRIEZL1m+yE9i78TPBkdiT2rWs69lAuzPG0Gtz3qHk09x2ZHu1phWL1BVYI9+PGFPYp+fz1TOj0+FLYHvod/0z2smrq9AL0ZPdSAD73C9QY+bpkjPuoAHT7sEWq8mJrXPYU4CD778cy997+yvJJKwD147qi9ReuzPZgs/z2KIB892qZZvEjSPDxlA4a9J8tqPDKHGD6Fx8y9gJ4WvS6HhbxJkVu8Bs2NvY/Ct70hVo491SKHPZ040zwbxzw9sO5HvDFLAT764Lw8RBnRvXYaKz0p0GA+A7GOPkUudDz1Amk8UpEjPcb26D7hCCY+","7tvZPRJmPz2MYUg90SrWPYEi1T3/PyU9arxhvt9JjTwY2+y9XL21Pc30hz0US2w+imB0Pkn6/Lx4FrQ7C2xPPm/xmzoYKrC9Bh0oPv4vNz34o0k+b0ODPqO9yz2Tli69lKhYvFmdzrxcnGS9mx0Uva8h3b1deQs+yzK5vbh2DzyeMqu97OnTvTutDL54+Wi9d4W3PQ5m/r0nbje+SopFPWzaUD142a49o20ePuRTub0Q22g9gcA9vfdwWD1UASG9WasrPkAetDyny069m5qWOxov7T2jSM091OVuvaLudDv5NHC9262MvaupFr0zgEa7O3bNvTjZnj0/gcm9MO9qPRUfFr2Lly09HSGdPGfKMz31IwA+J9ObvaHlIT4akCU+0ZhMvlhHDL3YS4u9/NQCPZGZFT4Axae9K/eOvSvyEj3MzoM9YsTHPbbRGzm1EzY961MgvpfFtbwOwxQ+gK0GvtbE3j1VXwg+UV6mu+mY5j3vD/E8Aq6gPVEUEr0lR5O9kmU1PmcEmD25fLe9LPdSPsQvKT448888y1AqO/gpGr6BOBE7TkFAPRdZJ749fA4+JPbAve+rB7zJeo+94cDTPElxdLxdYje9CNFmvFGHbzxTxDW+VYqQPPs1GT5F0cM9+6GEPdC54b2qk8y8+WuRPZZVGD5IkRk+g3LVvQ2CaL13EZI9E/rePZZvuT3BJxc96JmLPrQyiLvu9BQ+6HFZPqHmmb0pj3q+g0KEPN1N3T1ZU+48YDBpvAeOoTyrygC+vGGyvUr8Cbzyzr+8W+WTvCZoKbytZSm+Pz3BvKyZh7yCGLQ9obXLPZsf2L1xD7A9OfiMOvKh0b0JvgW9ckhlPQEbHr1zDFQ9FQ9tvZhDxr0V1TK9XaP1O0YP/D1a/kw9gw9APlxzkL6Xa+C98RYlvjBquj0mNCm9KcsdPvckWT1aqCO+79fWvNP/OT1MA+09Ptx6PSKHJD6HbgS+SMokPif2Kz41TIW9eLCjPY1m6r6/mRy9qUjwPWQ+kDsGgRG+","6NQ4vgn/tT0iYX69g9NBPs512Tr7Avq61LNGOt8J+bv6XWw9LnPwu/GcQb5B3429UC0dvlxbFDyWUxa+yEjovThZ3Lr5ins8eiVwvbdxMD1o+yi90pbJPSKwab6t9809ZZCiPDeL+D0LY4a+pHHIPIHR7T1K8oC+20wGPVAzYrzJsWu90DAnPWVVA75jPkk+3jDavAf6Ob0Y0Q89wq6VPS2BNr784p++XEASvjasQj3lwy68RAw6uz9Sp73Gt+A84S1APuuo671PoIA86kx3vhryW7xsUwO9PJyAPWg7tD2t5Q4+uTgEvkHPxr6PXlE95mcOPtMZar0eNuS9DsPNvZGDtTynuRw+R8i6vBZQfL4Pnwc92XWCvfRKIL4JDPm+aviWvmmugb1KWfq9wPADPLKxMz5W1QO+ju3jOu/V5TwmzuM8cMr4vZTu6Lv/wE87iy8BPZXjtjuCgXc9GsW6vbNHVb4qyvY9bH8NPFHozj1ma1a9ovCyOSK4ET7ypLa9eK/gPInb/T2HhPA8xQraPVDkdT2lWRc+AW45PU80Yj2aSf2+vnOCPRKuwryelRG8pfGZul+xpT2e9ic+EfcdvnORDr3h+jE7WJGrPD6bwD2g4VM+UzSgPbtvv7x96Iq96Ys8vhdtCb3pWv08XXlhvUQwALww/408LSdgvVy6573/lfu8uJRXvUGx972nUQS9BOm6PcivmrwMVMK9H6isPXzMS71nDZW9GQHavR6gp7xcCRO+Q2JIPp4Ju7073Zu9tpvJvVQYtb0gVWO67xPsvPAmLT4xaCu9peexPTSoob38BYS8g2GivAyHmb0dg809PG/1PINsjb4gkDY9dDH8POQOBr6ZZ4i9LFWCvTHUYr76YnA808ZUvV+rDT54abc954bLvKktBD2xOEs9EFYWvFabMb2H/MA8uCyIPALk+b0n97U9HFnGPdNji71TuOm9BZflPaopqLsEwuS8RoYOPgCPFD78GuO941Y8vQZ+5L3fcGu+MXLqvZDr4D3peIM9","XNnuvYnhGj0jsIY8sasAPvdCVr1rdh09YJgHvkxicL3fF1E+oj5lOxohxT0DHhi8bLUDva+6qb3RUWk90/IZvq3KjD2K8YS9bb3dvQpUBr3IpNI91eCAPb5jl7yEaNU8sg0kPVHwMDwzhj69s9sHvsiqRT1U9bg9F0hvPK23Zz0kO929yuSEvb1d6r0SE829JA5+vSBJhD1Y3vS9xQQZPVO73b1/acw7f08OPnUvOr00OqQ8SfIEvqoI3T2E6P+86coevmWJSb0n+9k9tqavvASv+b2oPc09QCW/vV9VVb7fb6U9X5BnvQ8RTT1Toc+8xVqNvaF5jT2xE3w9KCzgOIbO0z1w/YS6bXwrvkUNE70aSC26PTntvHqwvT0M3AC5Cd3tvNa+ej3IYsk9qonBPYmopDyoadY9PNT/vXfMKb09SJ09A23NvSDI0r1w1Ru8nX5WOyDELb2fc1E8TDcjPDSwpb1w+Xc9COEKvRIMsb1iWqI9rEPbPHGC1zykb3S+BsHEvcU2Dj6+TRK9XI5fPSOUSj1oGFu+j3NEvgNipj2Z+g8+4k1fPkhhizzg7hM9v392vcmiSz1+kw2+1f5avIlNa715cAG+fOqFvAilfr66tru9VVOpvYo3pT0p/v89sD7zO6pKyToILpE7iVMkPWG+Kb27QRs+LiIdPtxRk72O6p89/S4Mvizjd7zhs1M+0l+JvRUcmD2vlm87ukZVvgsoQT2x+wG+UNONvBKGhz0hLEc83Ia3O4SXTr1/9jm+6XNgvWaA9D3s9BQ9poo8OjjT0T2WJSq+je2NOz316z1PYY29niQKvoGHtD1bUji9GsKKvbPa3T2SZ4K9Pw2uvd9oeLpjXTg9QaelPVCEeb1grUS8YqCJvXk7WT1nL7g7fls2Pj5oaDwDHg+8h236Pa7Lpj2I6+M9NoRfu2mr1zxVRss9bNRRPbL2yjufVOQ96yKBPYxUpL0uXeS8C3cdvf1lXb14V4w9ie+vvKb/hr37cpK9ZuU1PnoJeTxtKbg9","wptNPLXih73Qbue9uAnzvAXr0j0GPdg80vmfvBndCTu5XIq9oYycOknUqzx/eyW+L+c+PZSSyr3mzqe9JyaGvBS3hzqLDQ49UXrSvWLlLL5Mwc09EsATvI0t372HyUs9WOH3vR3aYD1sQtW8TBCivcNoDb4eNKY9/slGu3TPkD0FGL89AOe4Od80+7xAYeo9OQiBvdGmqjvid6Q8R0uWPQpmtL1aFqC9G2/ePYsXpr1bnik9zEUevhw4n7zMU2y89jXXPfq3LL4YlCy+N2vbvasVmb35/Eq9eNmPPYQvrT3tKlC9SxomPTkJS70z9RE9nxxRveHZELpDllI80YfwPU4B7Tuug5y9vBvlvYE/3D3x/uW9sFJgvdaFgr3YiuO9uYygvfngBr48IcW7tKQ4PWk3gDzSaX29+J0TvU7Pcj2lO6y9WyeDPcx9YT6w0/a8fJ4yvfcaQDxp7e475Elevax6Dz0Qp4G92+kpvmixXb1CNiQ945PqPQObKb6qitG9LyFFvfXj5L16lau9cYq7vQxe9T1UNaY9ej3PPIMtib2r33+95okKPgOBhL20wtm9X7IxvOw2M76apAG9qTmnPNVNTz12hoY9TvRjvvF8TL3amWw+5g2zPG5Bh71kktE6czpMPTb99j2abbg9EvBFPp3RBj5xNaO9ECbyvfRAyb0QWys9Y7mcPZkzij3mU7i8ej+EPbFlpz17GyE96V8bPSvMDT0AAki+bSOrPT1lvD2v5Xm8HS39vZS4Ab6Xues8GkaTPUNn2DwGTc09i07Duy7pGj10AVI959iFvWz3pb1RLwY+WrpyvdcHjDv8Poq8aTibO5KjUj6ISoU9YnIOPQnA17ymjoG9591ZPp/BHD45E38+vuI3Pcwdhb0zuzs8Kga2va1iwrw5GAU+8C6nPYcN7z0RNsc9ZmJTvTImEb4OUwK+2JFlPM+GXz7ir2G84v0XvOBC771sUqU8TCsSPvbyo72BUJ09WJF+PhW2sz22QCK+t3ZSvYFaJb0QhFM5","mMs5vpONxT5UfA2996gMPlvfFr6ohWm93wu7PcBKor33Qqc+m/b2PeFc0j04xA+9EzwrPsnBw71RHdC96gjuvUkxLL2X/3e9BksGvlESnrwuSCy+FSX9PUHkfTzzdzK+A8PwPWo187qh+mc8yBuIvvPXYL0QzD0+6UYzPtJ11j0E7Ay+wUKVPaO5xzwH/Ic9efYbPMIkxbxitnQ98chRvXCoPz2elgM+kcIGvkQNFL24Q9g9jzQ4vUW3Bj6I7qE7HSeTOuE0hb1HCvM9efE2vgIUtbxqYAC86LjTPIc5hD3Pp9A9AzuPvfpljLyv8wQ+16xevHN7/b0w0549zpbZvd1SJL6Ru8O7mHpGvL7IAb17I5a6ri1kPZ45zDwpllA9MS6NvWiIsTwkJBW9esK6PWjNkb3SDha97cMuPrLC1TwlNBk9cDivvUjkj731N7G9GQLbPMpArLwxyIg9yRocPr1jgT3NUyc97dAzva+/8T19PqO5GU0cPogqGT3tZgQ9EsSSPLvFPDxjAIg9dH7FvcF2pL2fXxG9qtqNPae3QrySRpG9UI4GvczaRj0NE6O9pZGqPbIh2z3s6Rg+ObePvHQUq73XLpi7PzuVvLqYGT0HdlY964zAvRD/Hb6EnuK9LS0Ju46Stj0PVwe++e6mu/1LeT3a+SU+m11BPmOh8rz3O768Y45LvnXoNb4qQiY+amwuvEmo5L150pg+D6c8vnGBrT3d0ke9eEmMPMgZXr1Lm4Y+j/aGvYpQ/T4OTIc+f9SNvkyo5b7YwfK+L4dmPq/3NLxXaAy+KEUsvUfwPLz7wnc93SPAu5GOdr7lWIS9XI6BvRDZJ77UEDO+Ni7hPcoGDT1norE96zt1PgvPhT4acj++AtWyPjmdYL0bSmU+eEfdPQUgJr6+sp6+G7e1vQ8Okr24MZ08ChwyPVWCWL53bW6+WnS1vV62CL3qe169eu5dPofriL6KNOK7WHu0PdFgV73nz6w9meskPhHFG7+4rke8+iuCPdXdqz3VSCy9","UvtVPf2K/70Qqo69CPNDvWoqRzwKpak92gmjvVb4nDyRibU9zLYwPSXj8D3a3ug9XLa5vejxqLwg/oM+JG2/veNoAz0M1IA9Bt6NPWV3GT6bsEe9vKFMvawWxb5h3og97yGWvXpI/r0LgkC9C19TPVW8Qz7IcCy+N2+VvcUJKb5MkHi8WpMBPfZJHT54nvA9s5Wvve6SLz6OsYW9S65YPgERQz50kvy9CSolvgTRuj33SIu+CDtbPSpIML3z5Uu+BbtoPAAHST4hGwc84Q+Ovbqbt7yBplu+aiToPLnwZz3zM7M9Nu+tPUmIMj172dy8XU8+PojMSr51PRu/z22JPe5MDj5fYsY9bE5VPLE6DjxRcP09slofvmV6CL5Y41e+NqwpPlGYqD0QJ2c+KAEtviAPxz7f+sS97m++PP3msT08SFi9NGGEvmZ3xb2g930+3RSHvfpp6T1vqzQ8eGNDPirboDzkUL282YAHvPkCAz3BP6A95T0ivm8p2jw2EYI9Um10vInNHryfAKo9kWImvCk2YD2lZEY+KD83PnMCQj1WL02+arxKPU+XwL3kiHo9dMPyvacrkz0aKBe+9TCRPKogQT3aGq+9R32xPIY5pD0pIoc7XT0iPtPynL2w6d69jo2yPSdKUr62BxG8Uk9vvWDRcbwUHkE88UZhvOyItz3jb5Q+A5LFPIfFhj34W1U+uOSNPeivbT18f+u+UdB7vSmNoT3GrBM6v2p2PF4uVr7zob2838pEvqXgzTxDEJ49LG2UPT1Pjz6PxKK9c6ZCPagArjslApc9PpEAPS5LsT3gvcA8wZ2qvB4TLL5pbjY9tGfCPd6PLzz1BQy9lsSDPCp8rr6lvNi89vo+vhOnkL5CN689myJ9PVyukj4rhxW+Xb88Os73cLwxD6I8S8qsvdHtNz4riE49LQqGPMK6Srpu+RI+KI0MviaVjD4YOI2709X+va+1oL2boxe+CfwGv4MfRrzKcpc8I4K/umsKYD17LzE95rFFvkV6hT7WyGQ9","kNzePv34z71E8ay9ZwVcPdwWxj30eJC+mg6Zvm+Neb4/qw6/nXgMPq2Jz71Bjny+YxznvfkW+b0rZMy9Vw5QPtsdfzv2CSC++oo1vlz0Er1JzGU6bnH8vO2C57x5CZO+flINvgZWCTzde8i9NGp6vWziK75yoiq9nPHzvaC+IL5NNwk/2+BSvQ47hr2qnHC8URvxvnubcz19eBy9VaSCvl3I6D32xDg7hAzhvZXI0r7rmAq949rHPXhC2z5Mx5w9hqk6vS/ZN7xUVPC8KE0cPZWtML1+Cb4920hfPvUEsz4NRUc+xc/evjMdqLxZiw4+h41sPVQWjr0JBhU9od4EvhVMzbyKKVm+eBTRPe2HzL0Jxku8pmUHvaAzsbxuGe++ljYHvTe2Mr6iSgm+bESVPunDij1oQm4+IPcevtQPCD4rZwq+A3zoPtc9dT0FPi+9XCogvZK0hr0K0EE9I+XavfIxAr0EIcS9CogqvkNe/D2sy6y8MKW3vIDBTLya1BY+WthcvWq2jD7FzCS+QVmdPc07hD0k9dI+SlvLvolONr6iLfu9NG5hvr08fz33Y5y9A4HovaYs4L0Eq/29JOmVPaVGuT1iv1G+VoaVvsGCdz6xR2a+V3O+vsxc7TuROrK+37/0Pmab/Ty5oqk+LW0JvU1jmL7NNFg+J9rRva8gcr5YIYK+Ll7LPGmz4T3JRyI+OPCNvTAIrL6B8rQ9+uoGvVHidL2IEqC9fKw3vtv7Qb73qk4/Y7EBPS4EAD9G6uo+VqSkvcwNhD5mBlI+/CsBP4MoVz6sPRE/BHLuumzw2r7Lj5W+wLfKPBNo2D1kB769WX+IvVa4Mb7pF0u9ZOvbvWk2jL1dxnY+HUCOvWLfobz79sE+WlLCvT8E2b2Aq9u7qkXXvY+C3ryjJjQ9e0OjPYaFJb4ohKW9DQA2vQO8/r1XYfm+Tx+mvRlldrxxNtw8OSXJvISq3z4frTK+FT2/vZLVJT1XP1G+Cs5Xvaz/lz3iJb+9Uv+vvVl5Nb0BZY69","SOYzP7sAMD0TphM+XSnhPZ+9oL1YzUI9pTAfPZJHH7thuNM+YBjWvKvwJT3j3po+cyAoP7nrxz1ZkNe94pzcvfogXb1b6qG6aYstvtWBCD5/wmO9OgSQvJXNxj5Pwlo8dzlbPTdQKL2vqWo+x17TPYA2vr2efr081nMpvtLNJ700MEc96CeGvZ2uQL5GnaU+BmdhPSMtMz1idhY++MnivVNOzT6TEau9ZzEsvj1csD3pxok9kK6kvNhegT1UP2G9IuFYPiO6OD22iY4+HAMdPRDwlb1Fheo9bLpYvkvNqj3WHzK94RmgvkUKh7776Jc+XeCBPWd6Z77OVtY8Ii9IvkFfAD5EWLU8AeImPTgkSz1gPmK97AmrvUbMqr0PR5m+Xqg3vhv9PD7d3ti8Z2H1vcvP9r232kQ7O7jJPWti5T2wmG89C6Iyve7/gD4LBTI9NG5WvMCREL2bfhc8YuUrvo4vHDzvDYG9sfjkPbJhHj1n9o69ZhkaPrmsFL0dzKu9hhSePv+q5jxVkpQ8egvYPbNYB7zJQ10+TesDvcfi9D2r0wo9B3JcPgxo2DssZTS+aoV9PoPNDD19T2c+hSKLPY/szz0gt9y9wHuHvXN2CTwTDPy9Fl19vSE8rT1iJBU+kBkmvTKPlT0mNNo9Nzs+O54m6j0MB0g9MLZgvfmptLzbVT+8yhA8vdlT4T2h3/69iGzju9/ZRT5aF5C9xmayvUE2iL2VKQq+y8p1PBO28j2UfwK87T44PomDoD1whDQ+t9NzvQWWyD7fMpo9CGa/PHfwir7AWC+9gAgyPskJSLxP66C8eqSSPazFZb6ZhHY+vSmAPXQvML0GUaG9ieiTvVDRET5yEsk9YLYtPX2Uab5FuU8+cMdKPvPyAL6iqF8+Ly+WPrAxaT3bdY88AJOJvCkD7z3A0GI9nwQQPEwScj7KHBY+4BlevT63Az02r2u9PmKDPWecg7zPnzo8+VMAvQ+cJD4feN67RPt1Pu9evLuTNwW8nVzrPE47zz63gPW9","NKeNvqhvFj2Im3Q9i4YNPsGWyL2AWbi98HudPLDWgj25jhK94TpQvdxm/rx10ru61k69Pjw8DjsyjU0+B/l1PtMfHT5c9+I9W/GcPVQQED5EWBc+UUS3PlE+Gzxmy7Q9zjXNvQTIoTuwNG49YrSHPaPYnz0ycLq9vH2RvYRcXz2jlng9AxntPSYFnLzogOk9uwQXPmyPc7yTiKk9JfOqvU8x472J5hU+ZlLBPQXiFL1dVMm7GofNPScToLzXcWK838eFvZTMnbv9plk9YofrvO3Dcr2D0Vs+3BGMPXM10L1A0AU+tcESPnXi0ju7s449L6eFPC+8h72Ptz89FojAvZ57Aj5ScKg9zfDpPdx3Dj43BQG+nS/GPVstPj76ZPw8lvUDPswet72G/Gw9p2P0PIPvij7vysi8A0AHPZ9NjL0vUQy+LRS4PezsDjrg6YQ94Z6OPWGL0bu8EyA+ugf/PM4R1T2aI4+9G0qbPnWbkjxvxYs7oQAJPi6ub736koo9CnBMPg5LQj19+ZM9qzEJPsyDZL1JRM298P4GPtKfpj1Apy0+sveavVEHo7239Ia9cZ8dPu1Kr71NNBo9Bm+oOzQ0gD3dldM7HqeYPijLt71Kofa9UGXIvIkvGL7SLKs9szgnPkUXI76pywi+kc/cvGtH7z37Sgs9dVizPVklzL3xVC49JZ2jPaLTNz20j389vBBsujCAt7zSsYK+rOgHPjs/9T3mIv69nLE7vdSacz2JaKq8ZO4AvY2N2zuO+Ia+ImIgvjSeHb5ZZK09v9KBvYWIRL1Rz1w9a5+4vQ30qL2BVQS+wGgfPuNMjL1xUZm75d7rvM3zuTsQjty9uoYwvndfGD5Qqb+8L17OPJ27CL0vnMg9ZO9kvYEtH76N1I29KRyNvcIPCb3WsNm9iRHsvROv6T0mCD89L5dCvaNJUj2H8/27XKBIPa56WruYJEe+UJ9bvq7b0D2GXKw8034wvbRcqDzE+CO+Hqu/u46Fz7xi3JM9cjPBPLQuUz434HQ9","KtSoOuuDTTs2EAq+RjwMvYU72TugNIy9xpWAvb5Lez0MvXk8ZD0yvXAtGr2bx8O9bEJaPdOGgjtxOci9jseWvfYGGL5/Z00+y/i4vQCRFD2caFk9rwqIveB05T3v3XA9YJ5+vT8QKT6qxEA9Lm2ePOb4kDwpWOe9mkXivacAJDzdWXC9GKBFvmfZDj5P0Jm9jAFjvEFbcj3CUNs7Vv3BvWgVmr0Eax++Jc0yvhtgdTy1RIm9ImcevjbiHj4zQja+0clUvQG0mzxlgtW9EJ3VvRrIZj2SY6G9asKcPVYLAT7F3au9xWMKPsKQBr+DdeG8BlQZPpSYozwHkBi93eLSO0QuGD4QAVS86dYVvg34I70IxA0+CEjVO8A1k76Izou9lrCQPXZODr5OzdA8xyPauyCvq70n612+BJ3dPdA8NDwLHCs9uBH4vFHLXb0isEk+hUAmvZYLCT7Q2xk8idAdvRhexz2eaqs8K7EOPW85Z77fi4k9d0/MvQDMEz65dwi9HPUSPeoVvbwhOJW9qksrPeyGIj7VHdm9ORtVvRC9Lb50pJ48xvSavcao0b0UIcu9JrKUvZ7cIz02WhI94U/cO0FnQL2Gyhm+QEyuvGzfob3lQKA9F18qPsnvlj2Cfk+8hpWMva7ogr02/yq6/Nt7vlZM/j3sWuW7MplKPSTFlL73U4k9zc+gvKXN9Tye5eA5sMmUPPFTrL1bUbu7poM3PYC/Gr1h1Ve8S4lBPZugpbyUDrk9LlY8vlxfdb2auUI9TLwWPRu2Tjy2lNS9ecGpPAFwgb5oZ4O9zdqcPfg7QbzSaY+95Ee9PdGlhr36xXO9iy+sva6j4D0apZq9gwYxvXWOprz/dP68eR++vZK4ZL0LT2g+/3QqvWWzEz08USo+vN69vaeUobyPJuc5kZsePqFxNr1OXt69cUadPePgUrzU4TW84nC8vamc7T3dDuE9MEDvPSjdiTzM0JC8E4HCPek8+L1A+EO+APrkOgyfDT5GX5K9R/+qvfv+JL5jOt49","YYrCPZIqlTwT+Ps9xGU9PurUmb1TNG897UOMvZZDrjyinpI8kRqRvimxwT20Txu9+LhmPVUfRb2HD649MOCJPXSCZj2ri8A9yfk8PlJ8bT3zU8c9ylURvfPnSz7Hpv49mR0mPeBhoz3Z9qQ9G4bfPXEyYDtMJoy99juTPWyhGT4l+AM9ueLzPAy/pb196FG80GIWvRQpwzzkOd07siziu3bJ+b0njcU9/D7MvHWpCL7l1Jc9E8enPCL/xr2mfnS+imtXPjVNTL3isum9YKzZvPD6E76QxVg9mBHPvI8sJr0niMu8RhvSPBSptDoAg3o+D/fVvBTK8D0XXGo9DF4zvDS6G77mbxu8EZ1Nva/nKL1xDgK90uX9PIgMTT51TiW+ZfHrugiMmj20wXA9xVAKPtZkKDx4+aq8Ok5VvThsM7z/fxs9ig2hPQ/nV77VlD89hwzzvarmWb2A5gA+s/LwvFCepz0QQXi9hfNYvTClUT54bWe9yzFVPlCoU70SzNS63dAYPMjEFr7qVSM+z0e9PSwoXL2voWE9Bv8ZvRUsIb7rOSY9VfhsvlEdi74d2+a8RQigvbvCijw4yjm+FuZUvIO5krwX4Hu9KXQrPjCg6Lvd69m7EMulvW1BFr2kZVi9EMYSPRlukj7lIG0+F17XPJEHuz1e5JA9uv3nPT578z3FwkC8gc0pvXDZG71sMgk98Bw+PRE3tzy67o69o0xZPv+5fD1vVws+lEv7vNmGt71N2Y09W4EnPQPCLz2XqUe+NegOvVBN6L2JLUI+iskBvNviYj7MrXC+swG1PUYVsj7Nhm89EgdVvsigHT5oboy9UqY6PeTYMb1/xPK85IzwvVYjeL3kzSA+yXW6uiDcvD2Wakw+1hUIPsjfQj1kq7G90LA6vnVyEz30EBC9s09HPnqZEj2l5Zu97nOCvYYIgbzZ80A+XYAUPY3HID0oTAq+GkD8vUYIkz3XOeU9y3aIPTUDKj31Au07IRCHujNIyj28+jO+Xnk8Po5NJDyIjPi9","+yHRvL6RCz0roGc9Qrb9PEGDtb3e8Tu8XoIhvuZx871yaJ48pH14PdzF6T2xYBU+AfHRPGm9Wb0Ua4g92oGqvP7enb0nvyY+geMxPWiDFD50Iiu8wla1PYNcUT2nhCU9644MPnEznb2iax0+L45BvgMpRD1arwy+ClAKPAYPsb2j0zU+EbrhvFkRZT3vPlQ9n5+wPuOqZD03Rb+9A8gSPlSVUr3MvI880vIVPhbYSrwUQJM9bJIJPuIGEjx0tPA9q8ASvTNaGb5raYW9NnHHvS3OAr3RbAG9e0hivS7I7T2HLEm9gqcNPVR6UjzvSwI+1dW3PeJHUr0RtRS+xZBSvIkZg7401Dk9hvv9vf3EGD48MjM9U7l+PcapRr14eKU9+gSQvQ03Fb7eRX68VvVgO8CRib04MYg9qhqzvXV6oD1ocnW9YfBavGvWnD2lTWq936eHvX+1P76N23Q8odfKvJBmzbwbTYu+uwbpvWAa6D3r5za9Q6s7vhGbPj6pau08o3GQvt3SqzzRcAc7+mLNuz4END6IOeU9n+8LuT9kWz7NTUC9ARitvhIGwjyLWAO+sKFjvZr/wbzfcGW9JaZ5vWTLib0vpwU+Lg4/PpVJKb2SaMA8JL+4vRKmAr7fH4a+p5kTPIGIqr2Y1XI9gRgzvtWJLj1hbc89B/p3vq1gdD5Wcqc9a2F1PNEXSb3/hDc9IfsEPQ+bmD28FBm+QRoBPo3rrT0ruho+/zL1vMLwYD1+LCO+e5SuvLfoFT7dqm++ogf2vQhJVr4aWhA+WafKPGSXEj5tJPe8FSsJvgi88T21r1E8SQ0Rvs7pAr7BYD68Vox0vlqHEL59yQA9XLN/vLx6gr0VI9W9/KJSvU5UPT4/0q89wzhFvgCljz3YUj49LTSWPD8tjL1OP1C+hZbmPTnajL0EbM68nQqHPKwjUj1UJCi93cNAPLG5hzkec4i+FNN4vVe6ir5pcN49ReU0PddKvbsvaGq+KnJ3PP5xDbw3LUE+AIpJPZXJgD4heK+9","IaujvfXNuL3nqTO9UHLMvTbs0DxhIvU8fzmEvvU3mLyeRYY9bZMUPnQdb72S7oo9/SFBPgGgCD1GJBc9sQIjPpN+vr0Zq2i9bGJJvpoVJr6+sHm+yTQEPsjIDz1SQQU91yesvBnGnbxfttS95I6kOS34e7zFdmw91AYCPjx/tD3U7N48UtmgPU+erD1gm9k9EKYfvj7Ln73BLNe7HM5KPaK8vj3iJvO8uWYyvRkhRr3lge49C9uqPF4VbLv0g8e9NBRIvWjUkT1mT+u8f9K6O41v171vma6+1t3tvABejr0FFRq+W43KPfxvd71LDtU9R4HwvAkGIL2VkIY9gyXCvQZMOb21VHC6iMzAvU7niTtBfdY8OdJavFiBDT5RqtQ8zHSavugygz28tuG9C2oOPXr7PT4qOmM9UbeYPab1o72C3Xy98akUPciMFb1Ny6y9A1EevqxWy70cEIe++k1qOnZn6Dxnlay9TGjWvdJGdLx2hEq9+roRvqQWAj0/MbW9Nv5bPIxtB71Sriw9j4uWvRGacr48L/Q8MAAuPnotOzzqa/08O3qoPCMSJD0FoVc9jRwkvXaEV70AUx4+EsllvAbiQL14LIM9sSKtPUu/Vj12i9E9TIMCvbhBwrwHn7i8sgrSPcq5RL2jivM8XXWTvWlcor6r/uI9NvC6Pc26pj3wPsW9qYwxvIzIJD3aPxW+NBbnvVYUZz1sJSE9ZLQePtpCFL5EoNw7FMZOvEvqML2yUmW+RVZyPTrUnb1oI4q9OB4HPc5xPD1mmJy9ZZitPU80nL2R1xW9bAn9vaRpwT1ASCA9yjsHPRsnrr3DOky8WVyivQ35jD1Kq/U8UpLzvWjZnL1uiZA8Hu+GvRnJA76X/gI9326lvV7I07xKyMO9Ag6bvXVWQr1isNC9A0LUPeKFUr1Jp1u96nt8PTETP70vILc9yQegPAUFPb0zVAO9wp7pvT2yPL3cEiY9LovKvbRluj3hz6c96oyFvca7S72n7r29RmMvvY4zOrxMPRY+","lcbFvGKvMLykpUk9rjutvR1D9LyUsne8+JAWvmRtp73CMQe9DVSWvSeJPr2SgTU9o16cPY7GiD01ZCy+Av0EvntBer1o+0O+dkPGPbrcjjykVvg9VBucvP9wIL3QlEy8adyuPH0gHD51ggq9znIGvoudJL7a8/w6e7K5PR3nhrofpOo7nevbPWXs+Lt9WVW+FQ7qvHi6i71Z2n68J9Savb4hDb6WRX09iYMKPV3/rL1LXYg9Muc/vG3eGr5Axxs9tB15PObBWb24Ine+k7UlvmMABz1MDbw96RhtvdFDrzy6/gC9SVLCPU3bOj2mf4w95vXEvURR77zKZUG+XyiMPWn5EL4YVwe9w1UCvgDjFr5Cv6q9f+6vPBk3iT1LkWY9xe2sPdjxsb1YqrC9KHK/vbk8eT3LYRc+ob8ivtYq9L3SEGQ8KSCNOweAzLwg/I09m+NSvvPnX742MtM9JoopPFBoCr5lM3a9AYE5PfbvOb1Ofoc8C87OvbCL271E73y8azfPu6O/0zwcoMI7+gbrvaJhK74W7rs9YAC0veeHprk+VdS8X2LqPN+wI70p8oY9XHutPEolv7z1/QY+G3ZTvSVRTj4Fha29AmY3PTpztTx8mgk9e0rMPBKjqLwGuwY+DWh8vFOttb265pu96uO7O8oUrL0Riyq9Y79YPSIZG70nlXG7bAuCvYVcij3Htra9fv2LPOpgQL16spC7F1gyPFhql71Nuv86wy+9vfsznT1+5AA+IVszPdfTdjxSjDc9qQm0Pf1stj1JK2I9dxHlvOOnmTw6QJI96OHEvTDT0L1NTo095lOsu0QdNzwbv548981cPaYR+7y/HKm90rI5PdpH+j0SSOa8g4e0Ov7NDL5FTLC99ackvSc9OD1jmPw9jOqhPfGMUz36U288497dPTnm5rz1qSg7aTTmPG8xqLxvv1Y9y794ujG+8L0hxYs9MpO+PQNHFT3ruLc9wwaHvSmmJ71InSA9GcvPPdt0wj2pNXE9ZezKvMAJ1b2s6aa8","RJ2TPeg/kz2srxU9S3IVPWWi+jyRK509GWRCPqBMebzyiy69m76nPV6RsT0g/dY8u0oQvZz/jrr4ROC8Unx9PQtGhL0k7jK9dx7BvkdU9L0PtVI8So5NPZB6Cz02bt+9dGM9Pcmp8jw5WUi8ygcJPYTgjT0ij0C92eM7PdXCDD5fakw946FUPWKelz3j1wu+LcWUPf0EvD1bH7A9dOc6Pb4gYTts2Ba/WL7DPcb8Nj3B2yC9gRCCPDoturzQ5L+6LdMovuiaCj7x+Xg8v/C1PJ9w/L3w5Ay+ssMTvGKGFz5+L408Wy2GPFcvT705G72+/k/2uX/nx714W6q9yMwWvqn0DT58Su091qYqPRdIgDxU6AO8ky+avV9/5b2Ribs98SZXu1jujj0kjda9aNSjudnTBj0fbo69ChMrPZ+zYr7omY49PaCcPIUwYD03vnK9DeKwuxUv0z3I9bk9x/XlvXc1TLz+rXO9cwicvs39vTthHXC8Rh63uylE6rxjhhy79u3OPXRhEj1oB0Q9ETMeO8xOVz3ESM29x33AufSsqDwbPgi9RNaRvqK0VL7dG8q9+4FIvVFVoTwmJv49ElSRPSPgAj3PjoY9o1EnvLCgBT0Lwes9966xvXejnb21r4y9snhVPDLV/L0etgm/xW2IPNz6pDw7JaE9Jremvq1tET3nf5Q9bm/YO/aK/Lv33Ks9ZYSvPVX5Oj1/PLq9b33Kvoqfvj1CU7U90Q96PtRAoD0BX7q9QO29veH1ND0r7TY9odWquoOPBL3N0xO+/GiaPQQLTT6leHW8IuKVPUoojbyl+Ig9+VBOPa7lHzybFAs98558vaaqyD0niYU9VlGbPFU0dzo3dxq+IZB0vr64MT4T0CS9dyBavC2tM7zUzGg9svmlvizwaLuKQ4m8n/WZvHGTmz3Ivm69FBSBPRn8uL3dM3S91nwmvSZ/qr237Qu8e/YUvsVPeT0DUrg9m1eou7CQdL6jzJQ8g7AzOYuiST1YRoW9qlyGPPj+yz0Vs6U9","GA0JvhtY+rzeLeS8T9wpvSn51734Rzs+M3IAPSTwHz3Mgus9rlUhvAWti70GqsW5dsPGPPzXuTxqnqe9hNEUvTqMH76rbaO9jzOAPJif+zxgEqO8Bh4yPMTJOD0pgFa8HVJYPPCT8rrfhMm9EVLivfF9Z72X1Yy+PiWXPJXmHz7SaTy8IBWSvExvKb0JSfK8azUmvidTLz3ZNRy9DuXzPUcI8r06m589ErVRvVigWT7zG828TBKRvYuH8bwXdP09jeuIPQ+csDzsw1o9zyIdPnt6xj2wxQy9V2iZPZzMQL1UGiY9+lRzPUXXQz1wFws9on+UPoZBXb2JI4o8zZUlvWA1CD2pIJI9WtymvaH0KT4NwSE6PeISPU4CED4s0bM9yuxfPPUOhr3vE4+7/KQ/vaKRer6GGyi99gVnPSMEQj3eahq9DX27Pa/N6T5h+NQ99RPePWS5krzAkNC8VzPiPcVR1Txqw+K9PaqgvcWDvLu4Sru9YKHmPT0jAz2mqWg8OX2yvNj+CjzJReu94JarPe44Pj1eo/g9mBgKve3QNL1nWx67e5l9PbwbxT28kFq+ez+aO9rudz16K8m8hcCUvaPtyLvAzQo9sb0oPKiXsDuMXZ897MSoPYPFjr1/k5a9tFu0PUsMg73DGXg9t6HfPtvOqTz9BfS7ijzdPYhyIj09ipo9fcJoPSFmKb1qeUc9AAIbPUcZST2hDXg+TNUZvuJr3DyqYmu+UYjNPQpGgbuwruM9yRDpvefdKj1DvLQ9groiPhIGej651UQ+7hfYPdq3zb3cnR29f7doPjKqdb36fyq9dPs5vR5XIz40RJ07WNbSvH13Yz162WI9SdBTvOyPnbtfz4E9wm4+vujmWj5GQ7w9Dg3oPTkRfbvi4vk9KGstPhhE9T1fn7k9GFjDPYE/9Dx79Cw90YjkveW9cD3y1aE8BdhONjoPJ76MssE9T472vBmYgj2yy8e7Vh/7u+jSA75lWGu8daTDPemgNjywqxS+pz/gPQT/kr13vLC9","nsWjPaVNIbyEhwM+kd7ZPWkAA766lJc9eEVgPf8dJD5aRq296mG1Pfkmdb2OaUo8EDeyvRoQaT3mHjS9oPUnvXHlJL0PR948kpWcPTMDDj4kVQi+g/VkPqGrqTsEZVy7cvQLPkwdzzxNLXq8PcUPPki0Ub31WxY+EnYHPSFLvbx/tR29XrpkveHpqb0jj6s9pv2ePbUceL2tbMQ9kY2pvcJwvz4dCIA9F4yVvQOxJD5DjcW9SNLNPGy/Vz3siqW8C48kvkhHCb6TUwM8bnAQPrb1Dj42P487C8CTvSOGyL0ncIM9e7CYPdmxmb1QXfM987OiPKdqTr1+K7C9JfIMPmCsET6KwRm9Cu/bve/9tj3jM5e79HKxvYIGqj3utce8YanBvfsWET0HXZq9cZAXvk5Ipz1WnQI8gn+nPL/zwb2J6jo9AKzWPABhib0nnyK+20VwvXwhKL1O18e9/+wFvixql72eS1a8xVMkvfRq0b3xePg84daZPgMGrT02Lk08zCwqPf6SETqmXgS8DJiCvYT4GL5AoOq8Z8+3PaEVCruOc7K9bZOGPTFQmTyqp8i94PjtPb7PKb1fzVi9qqYMvcuW1bwL1Lk9o2GRPI+uijzhpxC9gwzlPOrjNr4ziim+W5WeO+PA7j3tf8G9a5OKPEk7w72ldqG+asQ2PdoTez0dpI89VOiLPUINtL2/jLE9fYM0vd+fOb0qGwU+gOnVPY7NRD4OTCg+m+zpPD56Cj0e4rw9Y1WePfs0CL3PtVY9Gl1UPnQgUTzu2Se+tUAwvZWFCj6pKgU+QKf3PBhF57tSW5s7CfuBvIkmUj0H8XA8MY8DPauPhr2CSaE+2THmvSK5ED7BZ+89dyMaPieXp71RQV+9HvLnO3BRZr2q0Gk9twiFvUyVnL0KEZU944Vhvmzc572E1L09TAtJPvtHrb2fbBm+pdbBPWl9eD79O9K90VyYvrtqb70RW/a8G/0YPm+hzb2YXq+9yCmCPdX8Gr7JNKM7GvMdPtwgar2KNDE4","zWWuOrINVz1oCBO90RsMvI/YNrxDQgY+Y6uNvZ5eNz6dv+I8STcQvCaRzD2GY549TQCLPqB1RD4E3We8idpVPF8oOb2j6FE+55hzvSRooj1F2Ua+9nKCvb90Bj4yWhU6sVGivdAeBb2OU+i94+iMvHwJmT361yk+Tif8PPyjGj7pkMU7D9w7PrsV+byHVay9hNGjvSxsFT5MWak9+LaavWSexr1PSoy8VgStPdNcAT6AWIi9MTMhPnStLb2nC18+VOqBvaG+sr3coP68eHIVPtharj3y3f27y5YgveqaZzyuxQy9FfDFugXdkz4mn549ImINPkXjhT4wpXi+ap9gvQW2Jr0oj6M8Ht2MveK/+z2PjQG+VIxevcFuGb19r3U9VH5avREAoD1hm6G9WhPcvRsQej5z3wC9kLB/PTPVlT62Q4M94qMnvgT56j2ELi4+w+2VPnHsgz7Vdpg95dOGPKL+yT2XOQk+TrTUvNZoBbyJmFo8nvsDPnbFOryRjne9J0ZYPdT6Cz5cp1S+8Tr1PTRdiz6YvJi+V/UWPeF2Xr1wOEm+P9MSPZn2k72y0h69KfFvPKuGar3xjh6+oKlLvYz5Hb1eMXa7CBWnPbWDg72cdkC+NtG1PRZPWD0IzGY+TG8hvVn+HT5Gl2o9gD/5OUGED70PiS2+7jlSPUHQID0kUHg+xU1CPHKtzL1Kya27S+lBPszVNb4tcZK9EDcTvpoeAj7KQjk9zG7OvXgzQj5XkEQ+xVi6ve/Q3j2s9uo9HLMEvlYcvL3+aiG9RgTyvbWtMbxjyZs9dKTRvU5weD3riKM9GO/XPDD6cz1sU049cpllvUL6l77a9Ao9MArkPViKSz6U3ae7OLTKPCFQAz4n/5s91VuNPbpj3Dy9WFM9OjtHPRdnbb0+sgg6HE9qPvrf7r065V09ROSaPZakjD02+lu+2zk5vucPVj4vUdi9GAqKvWs2iD2jI829IaUMPlo1F76sVvq8zfxavZZCRD6S9py8b8+dPZ9fPD4CmQM+","PChjPqZibT06pB6+epPZPT3I17wXVJm9wU4sPAbC1b0ypFA+2hUfvpqp5j177W093lCBvnNgVr19BdC9JfmNvaNi/LxoEDW+mzKzPW4SDLvG6pW9kOMhu8cRAz3b/gU9NkTtvGIQTj42MfA9gf0Kvnv2q7xgWNa9BnmOvVzvHr7jGh89Uuc9vFvPlb1chPG9PAxvvvH++LyXfYS8UFonvlc4Fr6XLg+9hx+dvdOS3b1iINk9B1HsPVrAj72+2cW9zfI0PpPmcL27tTq+A+NdPTk9Bb4R+wO+hSTZPKSu2z3ijSs+8yE6vc1Hlb3xAwo9La5pvEjGIL6mTT8+em86vs677j0horC8vCdSPVkNcj0dU5g8nHi8vRstDz6SrF89XXDpvHMQVb5ikRK+At9QvXcbHrhubJs9jgnbPKLN8rzjdbo9Gkk6PlM+671hhi+9mdBuPJ7tgj2M3hM+UjzUvULbkb3XhOs9IjoXPXJUAT3EMUg84ru3vA4kfTyvyFg+Rz9XO/wbhLxICa+9H/XHvXN3yT0dtwC8BCuaverSmb2RytW91vG4PcU3Gj4sUkw7ulWtPVTx1jyCGBQ8tAgnPbtNDz75Zwg9LCIjvpxELT59K0S+Uaq4vWbAMjzPwoq+KzZgPIKDX70U4qc+SK2avTIHP75Wk0C+nU7dPV2QyL2mcy++cgIOvlaRlL1ZIUk82BnwvVfsZT3wZLi9De4XPmumED1QWhk9NrgJvGnG+ryXKrw99ZMGvt1snTyGHeM9/QWevbF/tz1P8G49dOWaPS9yET6KtQi9C5GnvFtBGb4SwB29uYivvVvbEL4xFoo9jLGivNpW7LzW6cC8lqBAvSpNor1xxYq8gMNiPQqFKryNrtk98yLDvUQ7Eb41Q9G93r1QPoBrvT00q+I808dJvo9Rlz01org9x4QSvk8ayL2yCsM8smuSvTJ5xb3Ujp46fW5Vvr3IQz0rEhW+126pvaoILL6f+pa9c5PpPAg7FT7GIVs7U7QEvuF5Nj3zEIw8","4nY2PcYutb0EY4k9suwAPf0mmr4boYS+2YgHOzl0ob3SViQ+3qeKPSuq6z1zwbo9yj9Tvq7aVj4QbA++sCn6O7XNr728QVy9NFTEPVv6Az67knY9l41pPfFLfjtORVw+WZAwPMvZPr6Sjj++aLKFPZaD773O4Cs+ov0EvtUmUz0g+x8+BLoyvVv8wr2HYp895ISnvBpGnL2tDCW+ct9gvelhyb3GLcs8/XF3u+TFlL45f4w9Jg6sPc7vmL1VNmm7uE2qPXxmc704pn4+ZmAcvYoflj0+U5C9tSqJvnxJabyyPnO9V7kAPn8TpLxkG1W+P+xPPkIzQT1eTCu+Eo94vStnHL556bK9olQ3vVPtzrufMpC9oBGivYn4/T1oQNO90UExvjrd9z2E8EY9poDAPXJr2T2aQdw9NOmYPmWuA77KbBE+714ZPIndab+0ioU+0wT/u6N4pr5xSzC+2QoLvUlb5j0Y8CY+T++9PTsRGT5kO9e8l2l6PRKr8L3SkCm+OwtwvCfzW73En7Q8VtmhPH9sCj29v+u9kteiPUnpPz5VBtg8y76Fv3XNxr3z2yg+nWcPvrkwqT2qHCQ9UQChvXEXI77fb/U9iLSTPJiT2DxKGgm+pgN7vtACir0A+GS+BsbeOaynRz4MpMI94gQXvw7Lvjzke1Q9d7f/vblkTj4G7zs+TTYIvYtLhr3U6Q0+G4P1vF/ZND3VK1y+ZyHmvZjvAD4KwFG8Q5blvWUiLT3NxEq+n/geu4Hy/730lyC9rkKVPacYA75aol890HWTvZquCj6tl6w9RBzevp/Omj2+otU9lrDLvUpN8b0NZlM+vI7ZPKd3jL5eyXi9oK2pvZ5xHb6sSoq94jhWPpPlgb0rVri+fexFvkoof73sA7U9V+EBvsJw8L7ffb6+Z5xfPM1SRrxSBXO9HuU3PhlZj7x+2JI9RpW6vCORmL0gqH29EersvPp3jb0G/mo9DAIkvPGq1z0EsNC8qQ03v+qCmr1mTlM+A0YAvztxmL/ZAq89","QS0fPhfxYL3ncAu99Kx5viddpLzILnS8slrqPX/Eib/3TG++0dSWPlawFL1e9OA8bSe3vXM7jT0Ez+g90iJBvtcONb2FgIW92AMJPhg5iT228Wu++TYQvgHYrD3Y8P49tDrZvvgzhT3aDi69V3E1PQuGI723/k49XHaTPbW6ZL3NLP+8JAJ9PRBtMj7oKmQ9TKuOPOWAsj00zTA+jhStvHVuT7+ubEG9eGs8PUlneT0ORCQ8ZFBqPLpshj0oGNy9dKKWPYAggr2EWFI9anNCPu/DDL58HRa9g/Q6voDo970kI1++xRmNvbD9GTyB1qS8hqH5vY//Jb6f69g8pRKLPVT7nz3RRCg9O1aRvUpd5j3coHS92SVOvYmWrj2oCo49FYyPvS+u0LsMmUk+ZkeYPVX2s7u2jbK9oUTpPOYGM76LfD69ZnShPLCFxr0vOVA8XKjrvZxuSTyVu5k9G4wXvqugYD0wRgw9OsIePjwoBL5tWpq9/yG2voAJGr1hq2M+QwcoPgr4IT0p1Ry8BjH2vLgLLz1zlbA9IdZBPrL4/jzt0lG9omuAvhwW6j20vKq7ZVc+vbjCl7wTKxs+8/6muwTQdT1cQCA+LyFuPUela76mPbi9Up5HPeYPCDykVzw8jlDcvHazwb0+Mko9AGrhPUKDvzw5BV+901C+PJTbtTzYspG8fPStvLT0+b38Dua84gxAvTdMuD1I4Gg9W872vEJlYL5zw6I9iPXAvW2tGj13EOG97q+6vQWnjrwQ9i097I55vcPYIDqlsgA+CcFyPvxggD3b3xg+/D8hvmzHEL6aaDy9QP9OvLccjbzOquG7Be7dPC/i6r1dkKE9K7IqvhazhTwvpZI9mFe+vdWPw7yZGMS+8JhzvkGXqb2LMuM80zCGPYJjijw4oIu9LHqHvQn/6D3JQba9O1GXPjuNn70Bs0m+yHSxPR88Rzwpule+kqiSvYu7zL5Ky1c8mDyUPYS0ZD0U6j4+ylWXPDuoor78/q++kiy9PtGoFLx/C8w9","LtPAPf9o3j3fkG+94/9mPRwkLr2hboM9Z889Pf0ay75M89e990qFPoOm4zt72Oq8kD7vvdaIyj1XJMi8+dadvC88TLxAows+oqcsvrAYrT3TCXK+wUWAvQKGV76JVKM9bhHkPtGkvb1O1UM+9ls8vcUd4r2gd6K+XNQEPrp2xr2LNEY8EQaCved3oD3amrc6CTYJPiDyk73oQM4+Bcj1Pc3G+Tyfr0s9/MdHPh6v8LqLGTk9X7lGPR6zRj4wiB69i/NZPvTlXL3s1hW+wD6HOySn9b12aHO+fUQqvmoYmr3uHJK7JFnKPaSUxb2JdY8+U8gfPLO3hr7VBnu9q7PwvZI9mzzQegA9DGayPqpQf75Sk208DazvPajWzb3iUMo9PTphPVuNnb1z3DM+39grvdk63b4gD8e9/3lpPGmmvL24JaM+J/ltPLF3oL0uDJ495NGpPQos/z3Es2s9XBBuvhT96bt/+2+97vanPtenkD0JevA8msrhvKr3JL7y1Ra97h3rvaC+hD59CJg9Mo7CvdVgDb54tBS+zW3nvCjAn71QzjC+F0y8vOesQT0HCak8u7CKvvCpcT0t8gw8FB7avRP2g77GN66+pXSCPUB/Db60OS09+TpDvXbeFD7AHBE+tnoUvkE77j0gC1q9A8iYvY8ZIjraNpa8BPbCOgfLEz691d69GvNkPtkJ/72ctYO9VYCrvrHV0r6Bqyy8k2eZvlmApry5DKS9F2hXPkbTkr3W9ZU8CCiOvZal+bzxaW88Dh0gPXqdoj0BJ5g9RbXTPvSEOb5mXd89QdiuPX6Mdr2RuMQ9qU7OPQhmKLy5dVy+OHuePhqFrbzBagK9BStnvdv/CL21nrw7D1QwPTDFsbpUupy9x8AsvQqeIL7h6w2+WRP2PrsGE7zP5G29C2Zvvp58u73BSoq8M2dhuxusfbvwDJa9CvyKPXGkHb5lq0I+6Gx5vvQ+Gr57zQo9sniXvTvK5D4gy7g8PySEvp7FFb63Tc29etT4vuN/uT22y+W9","8+MOvtJK27wTQPY9UmCuPTNqjrxviZq9ZhUYvrZiFT4usK49U4iLvLCDpr2gmh26ChkGvoh/uT0gj0A94kg3PhOMpr3bSwI9DldCPpTRiD6pSia9qDYEPj8pTr2T5gc+X/GDO1jL+72KX+I9+ppxu5/Nqj0qrHm9ejwaPGCkbj2xXku9DJYvPW8cx7x5vsA9qkeaPJi3GT39F108J0qMvNNVOT6kuv88AvFuvVjD4b15UfS7D3G7veUetTl3+266yqS7veOPoTyV9oY9FZSMu5jcyLvAcjc+nCjNPEc3zL1j4uk9Jra2vKvK5jubJPc9wM+UPacXrr1ltSG+qsGWPULUOrsKiGs7jBzoPX3ID74G4Zi7JZ8LvesWuj2cms08oIMWvPd+BD2ro+K8SKiNvC2o3byc7xO9BPTqO16AK72CWMm9PqAevhbvQD2+Wic9KzJAOgpD+z3CkHq7PeIaveCjOr0omjq8r9zMvaheij0CuI29NFLFPcWkBL1K0yk8H30ivntuUrwZzts89P7CvOFYnz1eoxO+VX4MvJdPM70SxbC7OeYbPljwGD6lHiA8F4rKvZA+oL2kvb68RehKPa+Vlz1594q9mOqbvC/h/r2DZeG9ScxWPiMRp7xD5RS+mWC7OmtbRT4mjr29FGEdvQWEBrxQ1VA+uRcrPlQLnbyxj889bOF1PeRmdz2W5Ga8gm7PPAITyj2G8mU92/2FPkHuCT4s5ru9plDaPflGvT0ibQ8+78ibvZ8wbL1FSeE91xzSvDXZD75NVI68Pf2uvU4CjT0if4A9X52qveI3XT0P57o9Z1csu0Gl8r2G8Jw9ipqHvfh6vL25oWw961KqvOlYQT3rpqc8KDFdvK8gUD2kgOQ8iUREveH7mjxP8688ban1PYy37DvapJ09zmpCvaF9pz3DJMA6Scq5PYcY4j2Bii86/T5LPkgRQjxVVOI9TH4RvVKR+b1Paom8DI7avaz1KT6707Y8eSdZvSHEcj6u84G93qukvNDd+bz4qCE9","HV/nPW5T8DwJm7O95KwZPYyoYT3tGUG+1SYmvdA3/z2LQ2y9r4g5vD0eyztHpYI9ITo8PosS5L3mIa08++1avP6v6LwJ9gO+KWMIPYc8AL4CLPA98o9XOw5dIb6SSgS+OHcdPcqeEj1Y0go+o+v0PVlT0r0szwW+fNFtPUbMTz1ruQ4+Vp68vKpp3jzUFdi8mQU3vuNgiTsez/68/VBcPLt5Pz1pDpE7uQ5vvCs+nL1n0ae56WOzvICOwr3SRjy9m6B1PYtOnz3E0Py981c5vUUuRT5cL3s9WMAdPFnyAz3GtIo9cOCwvDf4Cj2CEVe71XqovT+lTr2wu469RIu2vTTeKj2RqIc9tv8LvLqilTxkra29ieLYPTELDz1B6gc+8n7Ovcj1pT1OCJk8hCqvvHHw3zuNw/48sl+uvJjCSb1tbvA8udIKPu4sPT1ccgM+O/c/vbEZkT2GrNc9Ed/CPCiYhb1EzqW99sAHPUBtkj0zYF0+FqQfPmKk/bwnOji+nbPUN7uQhr3Bace9RTvbvR0Giz78L509GDIUvV9B0D3AWL88RnjlPjLBr71vnie9fLu3PUGePL7t7Be+r+HXPXGWAj4VOZC9Fb5TvVeCr7xtasi9Xp8qPf0hoLwiLdm82R+OPe2/Bz1MlLS9e7PlPmhAlruV/5i9ykw0vglmFDo9FZO+qOOpPWcJML3BVzC9Nyp+vf+l0r0BOfw8P+ZJPkTlLj0QF9W8VpYTOzBO4z15doe8Wa3gu+evM716K7C84gyvPCB+CjuoxEu+7Z4OvgOSmD3euR09wAGcPhBbObsvTKK8riZqPsLvDz0OQi49o+rFPHtDzb34arS8acn2PfJeP705/CU92tPAPCvWOr7U5h29DIHWvak2PbxGYES9EssgvvIjqT35HpA8WfCAvWNTmT3wd+O8C78pPYKSjT0lNdE6X8cyPA7Xn71E0si9RUKDPL7F3jx5MgC+bOAsPeBFc77XQpM+f476Pukkz71ydiA+12ZSPv6WnT2HC5u8","GI4xvtvKzb2OATg9s9efum2f2j2xO5W91WWDPJdMnj7Fokg93ooEvuwn8D2SlAk95/LOvYIWij4BRCa91xGcPYtfyb2z6zm9bzNUvez1sL1fxZu9/4naPKdvK7y8/Ba71rqIPg7gEz4KOT89RaO+uRLfKD0LgKG92SnYvSP3gj2Go6A9TuQpvp/HxD0kJoW9DhsMvvX577xKaQq+Q4nFPMub3j3sKBm9tT/DvWE+NbzDNuW9k5ykPVYGgz1e9ay9jIaVvVVCYr3K4aa9rCUqPvaRRr0D/eq8XxSSvalX4D15fMg99j/svYgGOj3yRMI93WkwvasYQbw/uUi+QJ16OzkBUr3y1ZW9ZAjcPaCFjL2FUUA+rtjKvZaH7L1Sk9s87GTnPa589Tyv05W9JWtMPP2Wnb0zfh27YFhgvcMKwT2PpdM9G8SMvY8Su7x+W4298LERvMs6jTuww1++cAisvLUUNr57hjm9ZrNlvPS2Iz5Os+k8EW8wvak9nj3WHyi++u9CvbYJOL3N5Z+96fRwvbnVKr1Z+c48wopMvTu6Ib7KGZO96C+2PcS62L0tS9k95IHgPAoXir3iSg49AL3iPGXmDT1df/I9MItWvfQfzb1XyZE9eSQiPSbWqT2773Y+QWSFveBeHz7qvGO8nd8Mvq5AHb2su4u7RZ2YPHAS9LwymK89cemfPTpOnro6Yt48dqhxPYWBPT0vcZk91mp1vs07rTvjEH88cCRavK77sTz6js47s2gDPcEEB745wfo8Fkz1PXHzoT3mL2m+01Hive4Y8rzFfie9jD7NO2jkCL5HF6a6tkSlvJ8NST3zQA29z+u6PcL1n70fWcY8MoSfvQFP6jz7W5C9HWQ/PE8SkL02RFU+JuEbvoFVw71p5JE90zl8PucCCD7vd7C8wFvcPfv0Ir7hEtC9V6qoveqwXL3U5hg+7NF0vWY8Nj3uzMg8asQEvtrPLT2ISuU92cbCPGirpz2gfjg9CbSNve+mFj6Pt4c9t31xvJofcj3nVU4+","gVGcPW5IBD0BLx0+ZlTmvbA/ujztrvg8M2/4Pf42VT17M4S9U16CPQR4rr0AhJK9F0YOPjd+jDx+T6e9O8x3PVz4u7uFZ0M98qk5vd5RJz0N6rI9G9W9PTFfCj4zjhk+gtocPdwkxDsxoKq9ob+LPWOdTTx/J229Lbu7PSM7wb0FBVY9lx5WPgYCHr7uHwG+/+NrPKKgYD1YvtI92/DDvd7qYDy6qhi+GTTZvTqOYjogNoU9cvqsvbbta7zT57m9m0QJvciivz3453g9IPBFPeWJwLy8ZlQ+f8o0PKDkz7sfNJs9q58vvE3G/D1v8wI9S0kYvXX3q728QvI9rqh5PXUtMjwe2nK9DfWdO/5cnz030Ko9vVxWPb7dNr3RLBW+ldMEPIGuWb18f7y74LVIPtK9bj2l68s9crZ3vScMhLsC/4+9wOqVPfDSrryTndy9N2vJOzJ6Mr2pwCO+VtPvO98+m70d8d67TU6GvdZQQz2pY9I8Y/w5PSRWCL1jt5u8vFAqvc0AvLzeLp49mSZDvLlwhb2+99A9vWmOPWf6kD0jTy6+RZnOOjrpT70QmhA+ut/8PDTT+rwPKS48DT8DPY5h2D1Ii9s9Mit9PdL/Cz4Me0u9rs4jPdJHg72GioE9BsqaOxpZ0j1Jlss6M1qdPR3YiTwHroo8yVw8vfEysT3AuBw+uB+HO3rOAL6fegy+Etu7PHGVCj4kTjA9p3blPcCRWT0dGjK9K5+avdW7AD5LV4A+Dwy5Pespmj2QWeY9dOhhPXUfGj4fWRg+AIKEvYJx/z1KABI9CF1bPHf7qD3iYr693TPsvMsZnD3M4KE9iwaKOy8jEDxibMa9XqTSPJCypry5spc9OAzCvdC9qj2QNym8z4RJvHrYCb3y0ua8x6M6PP53Gb7Noas8RL5nvYRrH7yRYLo90vNOPWH5RLwsBj29clZ9vTwUkD0ykuc8HAhwPRFElz26IXs9y8G5PdjOAj3KXde9t5EkvBt1W73YkrE7lmXOu1orPjxbOoQ9","4lVxPj6B5z0L4R094pEcvXOzwT0VGsa9Vv41PrEYmz20SIC9aj+2vTrJOL1Ticm7R8TaPTzl4T3oQZo7ir/IvcBykbwqE2O7+bNBPi6L2LuOftA9WbQIPnjddrzk5MY8qSD/O9HhnrxTuIM941gAPgr18jvljtg9kOsWPhGxET2w8J895TgUvQuflj10Rhs9MFYEvnQ4Srz9W+E8moqkPaN6AD75/Yg+CjsRvpevDz7kSdy9JJbpPIyiCLw7lSw8hGX0vQdZ8T3rPmy9SPoGPTFHpj10glM+1ZXAPbUmgT4sx0G92vGPutv4LD0ujdE+v23vO0dYvT1WQPU9NWVuPp3BkD1doLY8va2xPGqy6j0Ij487FxD9Pdro2j3ZtB0+flTyvO6zirsd3vq81hSxvTKlRz6IOrc9NpWIvV79mDyuqOG8OF2MPc6gqbyzrcG84beWvf1sZ7v7gZq9bCXMPeANHrxRpw0+VpE8PonqoLugDbU9gjE/PIQnNDyoWw89GRFbOozcQDyM1PW8jTL4PS6cpL0cbvQ9AWegvW7fPD4wTL89aMeMvXx/vj2z3xK+5c6PPLpXBTwfp7o9cB9hPVHvkj0qzHY9zlldvOcazD2h/4C9708BPqefmj37fZq9mPqGPStToT3QJPE8w34uvR5VHz6QOA8+ml2hPcpP5D13c7Q8GICvu48G/btZDaq9vx18vAzZFT64T708JPbPPpJ6uL7Gaew8z5TavTVAMz4IVcM9rjJ1PiZ8yzwtRiM+jmuTvJE3ET5ioYo8iKU0PrxWq7zEugw+kSdyPRz0Vb1DUba8tugRvFXug70OXLk9MCIFPcvrkT0hrsI9T4+hPDNaFj47gBi+SboWPX1Gwb3LhZg9UElZPiZRhLyH+Zq85ngavagtgL2FOho9iROIPb89M72zmfi91qUHPXbRhT3/kTE8OqCQvaNiOj34WpS93ncbPdXPCD6NkUA9Px7xPBF9qDz3jdA9WtjEvaFbOrwIJKK93NS1vSby0ToLPs+8","YsfSPUlzND3RziY9MRXiPTOopL3EX3K9A3WKvPPLZz0qcxw+uneTvVShEb7WpKQ9x+xqPSsu9DuTYJK7kbxXvvJxk72fhSQ+HZMCPkL8m70mgq89GOJVPUW2vTlaBtc8RxzrPZX0qDzHmys95fDZvcEDAD2d5mM9TCttPQvFubwOt4C9lfbUPJ/zI7yhcLk8kTuCvaqsKb1fkCO99lGxvVseeDwvCpi96dUGvoxIoL13IZ29j0BSPd/Mg71yGSW9HOCdPPmLpzzWX8i9HnVCvhpBrz3tEre9uYbyvWu1BzxJ+GU9GeirvasOtb3gS2O9FJDJPY0AYz38eAS+9bYbPNXOnj69qZE9+oMNPlpF6L1I91899I+YvMkH2z4xVls90/+OPoMlZD52oKk91ljFvaiQ7zqXD4O9vTS9PaXwxL0+5bE9AUjKvWkYnbunUa88hWWvPTg+VT4Gy3+99WesPSOfmb3pNm4+vECmPS/DRL4kL+k9jmfrvGbhjz2qBvA7J3CIPvilyD0/wes8axA+PkmL+bugnga9RFsVvcSzVb5bI9S7Edy8PXbYyj0qv5s9LDwYO47bO71lU9k9oLnIvYHni76v0n29wZFSvK6mur21h6o9YaOpveu6Jj63zmY+mn9bPfCr1r28fAs9xL4ivpIMer0pMbO9VxC8Pm2omT3dngY+S1OpvU8LDj0+MAS+zre8ulms0bwARis+JlZ0vdHlUb0ePLA7+d4ovMH7A70MDag9nVjFvc/79jwlNK29O1BkPsd8lT332ym9CAyLvQ/0s73mAC49163BvYHHGj67/9q9JKDkvSLf5r2BpXy9Zd70PZS+gL0nWK29mhgrPmlQ5z2iOEY+oHKJvo9NFz5rjEm9zEagPqKtU71jqRE/cXyBPb6wmD6P0mM8rRgsvvpEND5WMDM9AkUMvACHD74Owua9D7kIvWVorz5oScc+lYzPPb9Ihb1/8Yw7s1wiPstoNj57TDM+xbTJvpdYGL1QJxO+NW7APc9JqDuymtW8","NGqZPI4leb1mOYE8/E8EPplVEb4BtH89QcC5PlBYEL0hLUY9sq3fvYnhUz6p/Fq9fwW0vdHimrxtMpI9NPvsPP8Hgb3e/b490yRDPv8zND65IJY9xDEVPmo1JT473US9R7kQPlHcs7xJLrO8sn1wvq5+gj2qilg8iFGYvAY2Wb67Lu+8Zt0FPuTxlDxBMlA81lmjPo+0qb06Qha9QN3ovK4TET4MjIW9JEACvmdBhD2FPzy+bHelPWBcqL0E5p083cUBvToHgL6M7qs8dQQAvkYdRj5LYJA+SoeFPXyETD3D04k+ybhZvXZs1r0oCIQ9kcUxPkGnqr1JnlM6GNEIvh22CjyfopQ9jDaHPYSS9z0DDl0+yxc5Pl88xT2tBBa+/TORPmWjuD3Hkg4+wLTAPb/LOb3RFUS+IL/qPNyuVr1Oyai6u05CvIR8Vj3kUjQ+O5NoPAYGoz0QVmM+nqm9Pr9sILxg0U28WGI4viarOb6NepK9zmoCPq7Uqj0nE5g9+TAuvpJEm704yYU9KasZPv7uNz6GL8K9D9W4PYNZpL2bSiK9DuMmPpniBr7jpp48QrVLPXYkgzt1uvi9/i3YPb2U2z1E2yI8Zz+XPSf4hT2Cj1C9eLOXvS+SJ7361Kc+HcskvW+Ziz3W2Vo93ld2PseOOj6yhCi9wGLrPG7ho7zrqHU9J4G0vSh4lD0zGfk9Y3pavOmzy7xMlEW98C0lPRBkEj43/069Vz0Euve17Dxykvo8p/GWO0yeRTx+WoO8wK2nvO2UKT6Wi/m9i0OxvZQWtr3juPC6JZhXPU/LNb67f4C9h6dFvQgs9LwHb/E74reZveZXlLsH8CW+fydYPWwaK75Og2A94qupPeg2r70ioPs9fe2ePV4fuz1ic6i9avfOvUsH3D3XJwS+jhLVPUckMb1alXO9u4xBvd2TBjz3w2a+Y8SlPbvhAz0ydua8rLgBPqUQMD38Yr+96GW+vfzMNL3h+C0+TzwdvWh9Jr0/ilY9Rk30vNKiPD2xkYu8","rPbTvC65mj361YY9ZvqevFD5hD3f7MA9uwTJPXyaVD1obrW9ocjyvVcKNL2R3hY8ZBFdvbDxEr6TGsi9xR4ZPfpMIb3Tp1O+A4fWPev/Zr0GToa+Li5iu/n/kb3Xe2+84sM4Pc1gND6yOxw+CCsZvi/HYr2NL289crUZvQsekj1poBe9bF3KvdfD3L2bTSk+Zv0lvs1AGr7XkBW+82teORbGNLzjS8y9rVQSvcFpjbzOlN48v/rGvJjaOz1FxwG+MjMBvSck1z0Eo2U9frHDvXRpD707jdi9HEAoPYm3Ej5Z8EW+WaDuPY6wEb7Gc8q9BtacvWZycj3uo2Y+5ugZPZgGGT7FzIG+FY8svoTxbDw9ZYI9XR1hvtRkqT4aIB2+NzOCvY4WBrya7wm+nGpAvoRlgL0sQy6+8N1/PRh12TuKFSi+bG8APfynMb7TF3u+T28mvqdfCL3zWV08SQZuPa6/ULwkSVs9UWsqvi3yOj37tBc90UwGPbrdiz0tzVC952IVvhOMjL6mNKS8E8IOPRN4fbznAD2+xPsDPRGk+L0XLc29VXJFvUocwT2w7qE9ZhOVvcNBfLvI1AS+lg6wPNrFDb0hDZw9igJBPQdL4DxWKiw+4b/tvUVJ07vzxj8+YjkKPl0wBb5PFZe9FbLLPQfyuT2ao6C9TamNPZ+FJ74eznG+FIZ0vOTODT4TUCA9o+R/vSj2Fj7jIXM99haaO0CVo7rJCm091nClvV/8Pz0pYhO+W/FjvZT9TL2kmwG9am+fPQGHoT0FgSI97Vb8PHtN9T0JsM29s2vKu4b/ijyQ8cQ9J7jOvU8Yir2iwjy+u+JRPOTEcT6TVZE99cFFPhJr/7yRCdO8rK0bvgN6Dr7imSu+mb6sPf4nMj2SKDs93VU/vmfbDbyaFe27FhgpvuchuDwXSCS7Q7a2vSpWA73dKu69TWiEvPIXhr2ZzKU8mSEdPoMl+rzG2Es+p22sPUkbdb0uu449fmiJPRHyNrwYaaG9/jPBPZWDjDwaqb29","ZL/pPfyyRj2BmRU97wyEve5Hq7uin52803gHvZxiiD2tmES+WyZoPdMWGj3siyK+F64RPbJVuzwMlhu+D0BpvfOKULxK3iK+lUH+vYxc9z3ZiNe8rUtDPkIfpDy6y8e8nQp5PN+dS7057/a8E82DPMwKx70+AX09BDMsPUazx73mRzC8ykYAPTrUHr62W5U8PNEpvE3hCTyxrh490HXXPZjtebwSykO+/UeMvN7qwj0TASe9U64sPqTEvD3oTNi98fjIvT28Sb3ntys+e5npPZiqFL6weMG9IXfmvDtOgr2Txku9g/xgvs6hjb0MLr289zySvr4VR70hhwc9c21JPQTQLLwm9Kc9OBUkPfrA5TxVSoW7INOgvM96R73HiV69rEGPPRy1or13m7C9y7PGvUbmaDwJH469zC2PPVEBh72qJwQ84BnXPTkKSj6icvK8zUxRvqJjgDyQ6S2+QiVvPZIwt71NGiA+tiwGPFVxP72uTd2911UHPsq6LrxEOAm+qhttveS13L2/fie+8vYyvdsIUr6SLBg+h7e/vFmpLr7bTgi+MssbvsZ5oz2A/m09gORmvMMYjzuVahC+mH8Kve+8ET6/VW2+rLHpvVGzLD2luOW9uphRPrt+ejx1As09FkeFPKXNC73mwg2+NXXavV0LXLx2lR69ugOcvp7LgLwKGwc95t54PU83zzyZAmW8dBYWvSmxJr3XBRE+JG7Sul4iIb0KxlQ9vVpavOIKGr28xmY9hKMgviuKQDyE5YK9344MPS3DCD5Qdsa9gKRCvqXuyTy52B0+5yDxPNEoab7QWdE9+7QnPdUWyr2fYii9EgMhvmYVYb2oBK08URsEvgTrTbyBS6S9WqBRu3Dc4D2D2Ww9BeO5vYe8Mr1LGVK8J4szvsbEhT1D/3i88ZHEvRY5qr0DlpG9+zZVvK4LxDxTCEu96/1vvk6jOT0bTxW8cqEMvso30rvTpC6+YTI8vSTezLvD4oE95iRHvfPm9jw7SHi9xP0qvq61o7sr67m9","8pamvKyBFL3/j969gdxEvc4OMTusaE6+tBsxPUXN9rt24TO9jk1IvLm/T71M+fE9VEtUPjAR+r0r4R2+8gStvV3i3D3GsuA9fgpKPDSkBr3aDRK8aXI/PcX6ID2fM8E9kZJnvWX9GTwU8Je9Ts17PFkXdL3tpEa+9FLVuz7xDrzo5iW9Zwj4vHLhhj03viM92r4NvNp6Vj1X4FS9oJN4vSJ07D0vUNe7SJ8VPUZ6DD4ItS89dofcPdzRlz20I508AbdlveQ8Nr6LXia+WWz2PZHD0b1I8uW9YntLvFzcVTxnY7W8cLDavYTTAL24LuE9ewZZPWe6KTsc/3i9zumVvQB0sL28Wxq+/rl7PXzwLD44ryC+P9fePLIYsTzBWYe+Ws2VPE8JIr0wKcA9/ALAvdcD9L2FPo+9GwHRPXV8XL1n+WC+NXo6PsPNL75j3Ne9lqiBu+TD172WAco9TVcMvpwKsz2mWNg8T1YEvvWEBb7NO7G+1ss7viZPErz5yXe+sI4lPPd89zxQOb69Pj2ePZvq1L38bZi9ZPcMu4aiRb73Ypy+RsImvmESyLwQ8WC9zlw7Pu56sz5C1Jw7/Hr2vXOeTD1PPTu+SeXAPG9Exrw8MQm+jzavvt72f72UByi8U3N8vlRwb759I38+eMv9PZRN2j0X8S0++2JWPf0Xlb1gMqk9n6uSvFeCez2G/4s8MJFoPTdXcj1jlU49ywmyvv3EYT2dxQW997ihPi/LGr4sWBW+LUn6PaCmIT6MQZE9PTDlvX+JAT720lo+2+NXPRJygb7ufTm+M7Y/vmNaBT16qa09uynHPQw+Rj0qqi++EgbSutOUC76ujvs9dihgPBkwaT6Xt1U9rWI5vjCANL41bYa9N7dQvvqAZT5drby+wpEsviF3jjx/Uds8BLuAvdFgL72HEu892xfNvVEDHr4hi/k7Dg3vvdXBgz6aHP09h2mOvKr3NT76poe9yoEhPnB59z1CBSS9kzK+vb9Ekj51Qd29L88Vvlodub3UKCa+","ui6EvCH5Uj4C2zY+lsp5PfrQrDwMhok9F3A1viEPwr3KJzc+2V46Pizq6TxFCZ+9OdSIPezwS75jtrW9GdQQvtUhWD5JpAU+gIk8Ps85nT11uL69mmEAvpQiPr7G8CI911kYvsVwKD0SfsM+EkA+vfFZ5T0iLJQ9OBvlPIfgtbxAyyK+Puh4PjQ3qjzwqwy8cQWAvaLN+b7LriA+RXgRvos7ZT54Tno9hiDjvT5wB74YXNK8B/ImvaU6+D3hjYU92ETzveoXcz7stQk+TP/dvaiGpjzR1NA9KPSWPnloQr02KOe8cX93Pv9skDsquni76ymXPr46sb1IeFs9Xq90veJ3CD3iFW8+4puCPep1vz3nvJW+bEDfvDE4zb3qU/q9pEZTPbO4GjyU4mm8nImIvjwC1b3UFSs92biVPkhKXL7d5lQ9rlGAPq3EvTwiLBO9NcsBvm/noDyMz8q9Bcufu9eHF73FVuk9LT66vfnf4L27UYk+xVkRvvld8T1ab8c918euPEw4wbxXe509LqKTvTbsNTyHHgq71Bu4vYdAij7iidY8cMDoPdH9Cz7peae+D3YbPcKd6r0Lch6+EXWWPW+k8LwkopC+sX4QPUUD6L1MEcq9LfaJvB+JiT37fIy+LTlpPvnv8T2JMJI95+H6Paw+pL076be+z2Q0PvN05b2aNGM9aAWXPcEwnLuqGe49zIiMPO9Gjr3uFWM9RRHdPVH1HT1Bv6W9DjYlvX3cVDxYVAM+Y2JKPX0+Cz6/Wks90VM/vmbmYDwwgd6+s5ggvohJ4T3rf7I8cZeNvRtRir2Ia3s9FYNhPVtbZj077w49twgpvc3zHL5cxUI+Y0ZJvSBhE76fes69FymnvA7rWT7YE789IpI1PlT+aTxcr/e8jBzSu9xfFL+fdt+9HTxmvlWGCr2LN4U7muu7vM3FZj2EgFc+s7iiPdGLuDwi8cA9v4zYvfTMqL4oFBC+xvMWPQ4o1z3G/b87A7BLu8Xuv75bwYg9ngMBPumpGD1ploy+","PFkbPf5GujxVQ/q7RaxUPdN74Ltjwws9mw+FvoebcD3pbh89ykUGPt1nhj2eqr69FX28vYtzvL1HC1o9XCttvhJgC76WXVC9QSCJvdiIp72PQfO8D2WxPY0QYr5NMeU8PLOLvCnbtz2QMne9BfOlvBIrlz50gAK+gQVLPUk2Er6bGyo9vLp8PVDDeL0hlWC8LEEnvuEUC77vXNg918C2vMdlYTxEq/u9ZMjbvvr7RL6XR229WLSDPq6IUr5F+gI+4l24PJvk5z3+hw++rFKOvi1Hbj2f1qw9SkJbva+vZ70iaum9KmPbPcKoKr/R5Rg9TtovPlhRpL4t/+O+l9smPQe21L0bGEG8vA7XvTf4l73cSKm8YkAYvSbmZz1j9Em/VF2mu092Jj43y2g+4VQkvkTVW7w88xm+Oe2Fve85GDzBjJY9DUAGvdxkwr1KeOk9AxgFPtN7ZL4r+ck9FbhxPS3FjzwyW408lt1svWxgTjy3HkK7CQ5uPmbH0L3+oK28F8SoPG/ZF74MlyI+0iTqPI16Sr79dgq99ZcwvA4587tBCtO+stSrvc7yI77jmLE9ZxDGPYeLkb0B0yA+mvrCvcJmAb6TVby+AsWGvC4VbD2F/Yq8ql0/vv0iKz5UVgG+E+0rvhRh9DzV3Zs8oNZoPaRD+rwY3Y89lv21PXjWpz0uemu9AOKQvJzdJLycUYs9H3N5vjpLnT4wNAu9T2OOvlZOC75NVCc72TcQPalCmL1OHac865divT60kDwGeyE+vw3cPPgzNjy93IE7y48IPutHKT1TiV280C/AvDQVZ71/wjs8G1Czu/7Skr1Mle+9f3o0PaY5r76SkfQ9hHbNPbWeNbyJwpO8fu79PPVT2b2yXTE+Sc+nvLtpGr1ibOk7Cs7hvWld6rvKD4i7lsccvQuDhD4YKoU9guY1vWrXnb2OHf89dg05PqkTFL5uUfs9EVd7PhInDTsR+t29rpeWPqFbgz0GzNE9HWaVvUJfHj1Dj9E9i16Avm02oL2B7iA9","GVBJvsHiBD5aNhk+dwMPPYBfXzyrh+q9nBJyPRgNw7ycRo89JctCvikOLb1lzlo9kUJjPbXquL0Frbu9tIYGvSl7i72O3jI+EW8sPZNDGz5sFJi9jgIkPrixfTzvqjU+CbwSPMkz6r0FYQ48XqiCvHp5Ab46VQu9+5IDPi07ULyjSz++oxJ7PUzoFz2LHUm+VMWAvhxJ7T0veH48IcrdPInZ/b3vI5s+6nIaPtpPHD4i/GM9S/7VPEvcQTyVUSg9ih9rPdx8K70fgjE9sMhFvLapIL7gYGG9vMKave3JEb5AesU97iEZPvqwoT33TKM+7nnxPapPiLzTN4G9FBQePmFbW72pZu26w+6IvL4VFz2sckm9J4T7PKAwCT4s/p69p7zNPAOKnTk9gq49gdU/vbYpDrwiFu27ToGHO3eJoz100Ei9Vr3YvfG02L2IG1g8Rbi4vYTbnD3UY4U9kAPwPDi3xL0DHMk8N/MjvY0gQL323Hi8hD4SvvCNxrzDZfq8q2ThPD3VjTydmtG9JPIPvbu3gz0x40M9JtQUPDtoY75Mbaw9kLAcPUAwij2/dMc74iwXviYud7v7lwI+QlBUu1j2p70/Bmg9s4EgPlH3mb2grCO+neJ+PQVKb70Tgn+9J7T4vS09kr3Z9jG+exVdvZV4xLxmQVu8KcuQPow3H7yz/sG8OPE5vjCF/7133F2+0g2DvAqj3bzQND0+YW98Ple+Bz6EpeU9KNkaPjlUND3YrnW+zpIavus/y7wVThy+/8advQ1FRj07New92LGVvS7nCL27zci+83mNPC2LAj708QK+bWwsPuejor0uHoO9ZTNOPVVhTL0KMSC9KqgTPJDLQr033w067iOKPIdcXL1iaDe+j++mPf99jz1abtk9WV0bvgq6ibwX4mI9sahPvWwQy72Xncw9PZomvjTmhj0PqTU8GuuSPbARhD1FUH87sm4PviWI272/21e975BuPaZyDL24nwa+CA+sPRLxqb3NePW8DxeyPSkJNr3todQ9","14KLvsIIvrtqDos92F+HO91RbD0bA1g+d+T9vVqBPL29lHW91G23PCHDAT3zflq9hLgRvkx80D1FcF69rFbiPYwdor0XN569dkjVvbNi+j2j9E+9cfVSvfXYxDyK5D69tAyTvfS2cr0rkYo9z9irPIleXz2ix5u9QRrOvAP2hjtIqg2+qz1VOnt+5bxtIsM9bcXKPfDIHLyRlc29ydz/vVSzl70/hJ28b1z+PJwOB75iuw2+OWDyPW6scb0EG8I8Q8a5PRR6gjp58Re+Lgp8vWCj8b1OD/g7VV0dvYPkFb6ZE4k9cKYlPlrUJr1f/AK+MZUivtIBGD6tROk878TIvT0Yez2DZbG56u+gPQH/AL4wMRO9AqRDPY2DYb7eXCu99LxXPjZGRD3uT328YESwPVp2kTvubau96vaIPZD6kT2NsFG9y7lru1bWHL41fCc+iGHQPVHllT2bP7g8i/KUPLTnwjw5SlS9ftfQvbJnkDu9J0M9KYkRPpWZxLzF6v29nWVaPZrzUjyneJg97XcOPU4GjrwHM2Y9EP8BvqC8ob1OTdi9Wj9DPn87F75jyHs8GtNCvtarhT147ZO+Vl3EvfGPGz6NSj69dPJ/PUitwjzfpQ48YInrvGXCsb1+Xhm9g8u1vIo9OL4yj6Y9bEqZPanT4j1WuCy8R8YZvu9sRjtGFZo9LM3evYiTFz6otyQ9x/ivPWpG/7uQocQ88f2jPbkxyL3tyb69pFcPPeqTnD0HeCE+kLy0PEFSPL7vDxs+c6oSPdSsFz0m5Z0+waq+PUxr6D3SgPe9ptUKvuyOn73JONA9P2s1PsiKyDwlkx4+u7uSPSVw1TyYr+m9MKa0vApPjDxLHoc98AIDvkrGOj7mhIc9qfFhPFd9xLxr0ke9ENcDPFYKbz6U3lY+xOaHvXy2Bj3jBv49gbOcvj9uXr1O1sG95KaZPbE2sTxwEII+fMJ8vRaW0z3ecgI+vGNdvW6zDz7PIJq+Xl6dvWtuID1xlCK9RAagvft6Mz5ENSI9","fLsHvUHN7T1JTpW9YcIQPncEnrwfHzi+IuM4vhO7ZT5t1tY8d5qOvDuiIL7Sy2O+22ePPQi/27xm/k89810RPWf+drxoh4E7wxmxvQVRJzx3FvO9dD+bvTQz77wQUu081iDUO+/ufj3TQOW9INGKvtNhYL1EWx28RR03PXVuAL2RkDo90QGrPIwmr70/1sA8B6RQPjiMGDs4vKs9hbFUvSJ+T74yCb+9GmEFvYCvlLzsQ5M9INLDPZ6F4jy3Ru09ss92PjBSZD0CA0A9eQkGvq+yej2u8f88IFipvd568TvjZfc9R/AOvhXMX72v2AI+TieVvUdxP75E2CU9lUJzPddDNr6XcRq9IiCDvXhmlT29jwy+E278PXjHD74V15883QRZPZD5dj2Om2I8C8o8vRaJpD11vjG++312vDqe0To/vGi8mrJFvq6fgrv9IkY9YUq5vMs0vDwMu5q9JimhPntIibsDgIu9m81GviB2jr3cAv2836u0PXMhDT42Lts9vjU1Ptg0Eb24p5+8PNCrPcmITb2RAUG9QfMmvlmvhD21zti8Gm8nPiXU0Dv8OBe+CqOaPJugzDxfHri9WlcBPebu2L0KG9U8JDg6PGUXAT2VoA8+uQlzPWprer01mA09YOxdvZr/o700L569durIvQ/gMb20uJU9+W91vEkMUr1t+jy+29azvSoxEz4p8JA9QwGIPRDrSrwUwKQ9cjHZPRGPfL091929hoVRPCuYer3otdw95Fl5vZbXKL2lL6Y9BtebvSPVwjzGlye+3rHAPa79mr3JDgu+gZ79PaibAz4r87Q9BOzvvLFK17zr1Fu9CsMHPj/TFb0pfio75RR/PodaDL5AFwq+E2N7vhEbGr0VZb48JxY4PprXhb3meSG9xBg0vvECkb6SFwa+CZMiPtFKr70rghY9sfvpvQpkbb0fX+o9PcMaPSf7wTzvCzg9iPn7vdcHGD1eDze9GVzru2+jOb3kyNA9vB3LvYHJKr53Ni6+QWAdvqOnwT0/QhI+","XeDwuksrhDsoy5E9wKmEPbPAbbzjFH69WVQvvt1jAj5Xe+C8FUyhvfVWsrxZ65I9g+Ugvf2p172s2rA8bj7ovRPKvLxRkRa+JIZrPrSBB77qLt69BIaxPeUzLr7rGPe8UPs6Pdvr673SCWa6GSe0PfiHnbw4zxs+wVQcveZJfD6kHOK8kVDMvf5BSz51uLE9LBuuu06LsL3rQYO+D6xBvpuFB77Ubq+9JNYlvnimAj4r7/C9AOjBvWIZHz2/uYA9+Ox5Pb2ffL2FMZu9Jl2JvX11F70qMuU8cPuwvI2ztr3n0AM+J8i+PaXjQz4reLg9HghNvYNj3L2VZri9YMT+PVfqiT16kAO+YHcxPHHSm72yKnQ7coSwPbMzxTwPCmK+rJRzvY/foD0tOSu92FZbPu2Mcj1rdgO+5wP+PdrG/j10A6C8ZQATvklJUT3cO4W9pKnlvPeEaDw0R0m9G95KPvLW8T3g2Gu91kzePVxHxj0d4569bwwCvobUQb1z3RE9sH6BvXinEb1fT5Q8l2IJPu30dr3ilB09KaANvlPuNr3C1y2+U/ylvQEd8z0TW0g9qZTJvf4Prz1I1GC9Tj7XvYAItzxuTg4+/fK4vZLgqz1Vju87ey26vRGR972dCsc9xnM2vgKOXz2uq7i8t0KWPdGKub3S06I9wbL0PLBmiD17JsQ8kqvpPQN9QT6RciU9UeFRPqs5Vj6aXlm+lQDrvcXHbrym8Re9ei55O8P1oL1Umne+wwCJvY7ln7z6/iM9EprGPH+euD2wlXk9x6AiOwDS7Dy+7qe9VGEmvcfx6D19PoU9gWTSvKJBoz1Wd2E88F0RPZqS2r1hmXo9AfrHvMlQtj34DkU94H39vROBjj1QISI+MwisPbvsGDxT9o49rF08vu+ZZ71EVES9RifhvM7mKL659Ae8x2u2vWsuGL1bf7c8gdRPPppnSz2ItBU9yzr6PVwv3b1LFqQ9r9/6vdAJzj2g/DG9eC4APXRIG710NNQ85rO5vM5bA75w2es8","/bM2vce1ED1mSu+9PjxQvQ/m/T3bzSq+JwWRPYwnl7xgC2u9XBQRPq1Vgr3SBTm+PweyvSegsTxtUAY9C1QevQ8mZD79LFQ+GUSpPstPzr2Yoyk9zQ06Pck63zxoI8+9Ra7KPLkwJT4qYKa9Oas4vTr4Ar5i4wa+LSznvcRio72Pq9c9viZHvRM3S7z8t10+ICwUvoz+Tr4Utr29zW5AvhDvlDwrZ4k9Y2SbPVFxoL5a5So9q2MrvE0S172dVtK9h8OOvW5NG74zrtO8bliku9M3l72c5r09/CEyvT3DIT5pR8Q91orbve/BzbvnvRw+3BFHtwqonDzcmxc+pXqfvp0ocj2I4aG9fEjHPR5aCr6JfZi9FLePveDkXT4YwGA+eS3vvRkaVr485y496PMVvRODHD7WUMa9f2J0vZf9gbx0xcu881PTPVRZBzsxbhc9VbK6vVefar3NHvO9HNTSvfrnIz0K7TW9jqjJPTo6eL2nIQA/SFEKvZqhib1Ee1w9orQcPlRBFD3ueGA9Rp+Uvll6uD2jf1m8NRoovM3aZT0/CAg+KnUbPODTbz0puyA85nHevFifYj0kzua8GWuGO+4NSz7RvIC9iLG5vSEBEz6nEMg9RcGavmuhoL3aEbq8NQOrPfnHRL7ntVa90hGbPastujo+E9M9tPo1vltWAr5tr2u+Q9MrPle1FD5YZpc8HhUavuS5xr6uqWg8oEaBvpD6WbwCvay91KhovoGWzj2/Mmg9GY36PHLFc71hxZu8MFkyPdx/R74IpOY9P6ClvWixLr5emVA+oBfRvby+n71LJUU+Z9k/vu1s9z36/Me9i44yvTlK9T2BkTe9o+MIPmKySjrKqIU8SyqLvWO7/zkwsfU92kq5vdh4Pbxy09m9br+IPWn9KL3DH029IMxTvlNuIj0lCgU9tOPPPfwXS7wbdr++FiiMvekflr3QiAe+u6G5PRyUqr08A/48cmOqvC6yPz53pXW9NfZBvB/dUzwwfHq9+AEPvj3ISD02cOe+","7TAzvs1mM73OvcA9OFkJPspX9L4dvce8wweXPbdmCzxrUxU+MFiyPVsUgTpMKYi8ZVZ5vdC7Sr5WrL67vIiNPWLY4b1z0dy8v0owvhFDNb6gA2Q9Na9CPjNryj2rc4E+Yl2ivZ48Hz3LBOe8v6uQvquIJj026KW9Y6JMvoJmCj23myC+Uj0pvX/M/TztOji+qfAOPt3SkLuH6aU9CidVul64iz4XxrM9qvPcOq+YHT5zxio+ym8jPZCtjL6s3DA9n+WSO5bm4z3kb9M9KxOUvTjEwD2yx3a9Z5yJviOoa74rqOI9IRKtPSTtE77gfb89216zPSRzs76AuRY+r4wfvkZR77p16mY8kL1pvbRbHr6T1/A9iLqxPX0CnD3X02U9/eSQPYhlbz5RoYi9YTgKvhwV671iL686RhfIvKDMoT0gRgE+JMj6PX2jEj6R/Z+8+QVeO7HFOj0tT8I8nNNePZRJAj6OWD6+BYMXPnL5ZL3ppw69rcW9PMJKKz66IZg95kMdvlJhlruOaw482MiCvPp0yz2gD4g9DjkMvbGkCT6MW529SgAePr72Zj3z1zc7a16GPSQKNz73Gow9SL9NPTcW7T3UVxQ91pRFPuSx/Ts1fDo9QseyO1HkUD1jh0e+fDiIvSZHyb0eNnm90OqDvgXBorxh78g9Fo3BvS2+hTuGH8u9C9++PcB1jb0yWfq8B/MlPSwjgL3GAsQ9r+n/PSpYnLxmnnk9kYZzPf9oVb3iDCI92mWpPaMV1T2GnIW89P/NvYatmz3afLk9M8+XPKBYSb4roXW9qCOVPkKr27yhJQw9aZa6uwWn0j0kxi89uLMEPZgSEDwIkvG97BCJvkZEUr2efV48i6cIPh25DL4ln6490ZPXva91Qz3PWh2+V1GBPTsHez3oPgQ9M+EevK8Qzzw0xkY+dfccvqXFNT6+DZu9vZVSvFVWUT6Bz/i8WFfuPahM6TwLoUe+qBjiPBUlgr1TkwS+e+KOPYYncL1JGJQ8E+2ovng3vz4iNYO8","Ns+dPBpuij0WQsy9myVOuyqxwjwx8+y91OcevjXu4T2jCbK8thIfPWK7eb0X44g9Oi2hvW7MmL2pX9g9qg9lPZcxu7tLGos9DrtWO/Ufl736qkA9nAVwPCkNK72IxMo9NcrlPGataL2xvBg+vZdGvT3owL1XJ8M9fr8OvlQjpD3R+U29uqmmO+ItDT1FeWa9EEaaPWrH/b3172o8MqhMPDeHoT1w/tQ73pYhPCvt2r1OgAY+tyhYPdtLkD3Of5I9LxubvS7MMr1ZYvO8QokNPn3d8L3ElAC+2+UVPaLDcT1M/a09pSfkO9aBfrwwZ9G852dWPBxElrytKV29ERyNvXj7jL0tUri8aeYevYWHaL2M/Ug+dL8cPq4+A70cAIK9OBjdvdPhEb2WJ9Q8e1sSPnWiTj4wwGS94tIbPiXIOTuc/J29S8IuPmUVY71611m9ui2DPIlEljyu6gG+njXOPT+EIry1Zf286cNgvTrYhTwQqso9nO14PIg1M71Wby270HXvPUu24jxNns+8gaxePDH3yD1ZqE28FEjMPSLgG72qBia+ZMZLvZTwaT02NPM8+uzHvJOmET1rCHc9HiAPPtMI072iU0U+vaDuvaHPUr7d9NU8wFPOPd5efLwAXUC+kBfOPdEG1jywTgo9gcscvhvaYb5rVDq+XtmJPf19m7xVRAS+LpEtu3HWVb3egja9lO15vG5vOb2PWlK+hGFEveGfezuD4VK+GBEGvvr73r0qjQq8b3QxPTLFIb62pV69HLiLvRM7rjzSvJ49aDYKvubmrT1/IpW9B10gvtUgf7vutIS9hH7IvdGYFzu6kBq9YEcMPtR8fj2RncO7ILvIvJ69Bb7OJg688NRMvV1ES77thjW+CBEhvtWbFz1pMXC94XQzPEDU0D1TzBw+CnmwPBESD7ygPAk9yRebO11E1DukFBy+DZqvPZ2JE70OhYy8sK6jPEWjwj0D5Dk9rQ3zu60Q/b3aJim8dZWgPMThGj4pRb68jshUvSu2ALytYyU+","kv4vO19ltL3Ycb69A8rJPRskSruBVA68J92Nvb6PBb3ZapY7S+ifvcJcyL1AipS9O279vMpSOzxdO8s9t98EvsqIIDzb89i7r4TqPHpDKj0MXUi9REwiPK63n7z1ySQ9CtTGu/IBE73yL+m9Nk/VPZxWrb4FyoG9iKuevdpOJL7eSIc7KsZxPSzwfb2ygOS8UCaCvf8I2LwFO4e96nnavSUevTwA8+s8lFcbPi1/27zk0c+9SgedvYvpyzrmokw9guThvfjCer0d5XS94XqxvV6L0jwPsKS9fw3bPCeDdD1J6NO9ddT4ubQtOj537ce8yxCQvavb1zx5GKw9v8ylPG5pJr2emvI9LdPKvEy1U70BldE9AajJPQjAhb5Zc6A67Iv5vfytyTxm3ra9U69evUEurDzdH6e9dXalvfK1db3PVn69hGKuvAFdszx42SU+KDx2u3KIU72OPnO9JehyvYqK9r0Njqo9nV4cu+5PoTxC83e9DZZKvPJis718/7k7LrE/Pd8L0z0Mfrq8jJIFvm8/oT3S38c91AZmPITlOD1l6AU+BVC9PHvvKL4BWdu9pRoHu7bmCT2aelg8vW+LvRueuD2MtVC+ygGlO6bYvTv1czO+J+eJPPvpIr2fRoE8n6WSvQCNBr3TejG97RXSPGZvS7xnjle9uD2svIKam738/C69dUDQvZMs6z2/f+y9xIphPmwlUryehbI9Yq3uPU8ThT0RWC29NGImvg6Ojj0fW0s+WSuoPZH82L3g8nM98aFePRxUIjxbgeM9y1GAPbNVP7w9hpW9wjTcvWx0QL1yGyA9hw5IvbbHjz2xmY09FBWYvcb4TD0rFTy+OWYKvmod/b08/N0869GMPEqWLD5VAhG+NfaDvM93B77LP9W9xMCDuzKFE77IVWk8mthXPXffNL7YWrA8z3TyPYbQiD3p/iy+QPBlvVf01r3amFS9VXmsPQzn5rsIbMq87mpbPtadg72tT8C90Am6vag9C74Vox69DaEWPu6cyL0FEhe9","aT8AvYQ30zzGaU298rtHPReBVjy12rK8UggrvXLU1z2DFL49a7txvH3LxTzIy3k9yCAzvpppdrul/tq8rLWTPUlcEz0zjgG9bgOxPegptbzkArK9oyqcPDHKSL2cma69pIEivTFiBL3IIoE9IoSSPW+OXDxIdlc8uzLBOgbF9zzymTy6sEcxPdyzXTmwJHc9ma0bPm7FAjssNmQ9FlAjPsMA7j2E2KY9l6cUPhdeAj4SnMO9SlQKPauHSz20ga26KB9gPTZ8LT1ZZle9qHmbvXzTWD2Lw8896MqLvVtvGL4RkL482859PSfqZD2GxOA8y7gYvETNA72QF4K9uscIPqOgjj1WaFa8TvyJvBPpk70P1vs8O+qCvWOYHT0BNpO9xRgxvb2QH725zbI9XNEUvk5Eg70GGLI8pNDQvXrqOb3xmoO9Xv2GPeuUIz7/MCU8ZYFsOyPBEL2TsYQ9jBPtPBUH8Tumgr89atS8PX8tRzw2e349k4mMvG0stj2ccrE8hFOCPJ8tBz7oEQS9/TMhPkVlD760nEG+BwxtPDWptz3qYcO8VcavPpt4UD6mr7A9EYNWPWB8EL2Aa4U9sbcFPUzM+Lvxdj09Gfq+PdCQN70jOSU93xPHvb0XSb1fDdS82kJRPW2NAD6z6BA9QOCDPKqSzTw5cx299M0+vVsgXD244hy8wkzyO4kWJL309Ha9uCExvbCHEL1i3hM+a9/+PeGT3DxTYmc9IKOcPe0iHj0XCYE9BhryPfIu4zzGvhO9z9KdvPGuUT0FCnI8cCluPTNKiz2wdUY+GcKVvBknP72AsKA987UgPYdrgb2m4RS8UjbsuQiCV7wFHxC9UWOsvDL+pj3Kdia8j8rLvdW8Iz2fxzq9/eUwvuAIoj0jIdG8ApiePfpBoTwYAAK9wy9bPBLXOD2HDcs9/zaEPdr/cLpvq7e9+XabPf9gLz00P3i9XyHsPGzdnDyVEaQ9lyIuvpKm4D39pbW6RoNCvV2wIjwc13s9N1YgvS2++zsMVi49","m05kvPd1szvEJiw9h1ZoPYccd71Jd9q79n1UPSwlfj1d39W9+wIPPSZVWz3JEsc8WkHFPYpQ2LwOBd89a0w/vBFnAT3i74s9dSytvIPHEj5CLiK9ZqIBPXTcO7xquAU+lQmCPZOumD01Wye9+P3WvC5zm7xhlIS9Fxg4vGbgvj1zqpW9pE6svfyLVTxcgqo8yI/jPUkNfz3DLnM7j8mrPQDakz04uiI+Sv7kOzHUYT05UlE9xFEOvnxTA70dMgU9hDGJPTBt0r2AmCY9MFrJPZYtgb1yeGk8oXWnO7fEzDw3WnY7Wr6JvE18fL0HpVW9Q/J+vBcMeT3WxEY+URaevW7mErtXQuA8vZaMPRD1Nr6r0jy+VsYkvSjU/j1qZDk9rJgmPInyjL3WEZ89GPKaPG8PFby9asQ9WL/lve8cQz2WJRI9Zt7NvP7JDj0sn5c+JSOvPVJewj2tNQK9+2uPPYxnXr33Jlm+2HOKvSjMCT4H3hO79t1UPaL4Zz5Mbsm91vmPvf6YSDz9kQo9ntd5vC2WhT0Lehi8BO5/PJTb0j08/aq99j1uvlAgojxpKwq9Ct0/viN3971+bkq9HKBxvdJEgb4yyfc9deKEPZSVvz2D5BK9AawGPU5yBj2sGlA+IXN1vu1Tjb4tymQ6IOaIvW0wbL5ipV090xnmPdYvcj2QTUA8TRGDvfW917qvhx0+cOzPvHXJ5z2C+tk8KYW3PYKmgL3RUtg9YGqHvVX30Tt6lpq9Bjl3PUfknr1TM9E8G5zEvZlsUr7IIIS90M1pPAbnAT38VxG82pmgOleCIz4F33a9sF3LvQ20rzuTxMk9tksgvn4KQD6Ifw8+Z6SZvKx4wzwju/A9Sq20u8zPCz7Mt+89d4UWvRG5jr02H+W9ZXAAvkRRD77fLee9NBHLvFT9pD3WzAO9NQbzPd/ozDsCT7e97/NcOyPYxz1jIcO9SgAdvUakED6tB3W9+CJzPAMnfL0+5dW9ro0ivv6ugb0pG2o+FyX7vYTSgb3TraU8","/m6IPnRn4rwBJ529/nmXPKCOHD7FZze+eI/nPWecxL6tCwu8Kg2NPYXwrzy6aLS9JVtWPS7/Pj4W9EW9dmkGvutCnr3q4v49RW4JPoYznb20rOE97wSGPNeOxb0lz+29GF8FPoc3cTyRZHg9UcEJPu12u70xhYI9CcCNvfIdxb0dnaU9znAOvS6KGT4uCKi9Prd9vUvNiT1aFYI9fKoYPQoQJb5W5aY9WX6wvWZhND6vupY9nMtcPZYphj1Uwts9TqcivuX6g72r1r499LXyPN2fQ71h+BE+PgyGvLMxsT0hOA6+QSbbvTV6cj2m20y9od4/vUH3Xj2pBP67XFv6PfL2Jb6YWoM9safpvYJFIr0RfRw98mKDvPoxvLyR+W89Clk7vc6szbyFtu07vQX6Pfmv7b1My+O9hEHgvWGKHD3aqNG9rwTsPa7WNL1FYiA+y0kNvgAnqT2oLgK8wKj7uoaUAL0bzVI9QPPavWMDNr1JqYi8TKOiPRVbNz0lIlu9y0QuPgJgLrwHdNI8rzeovcTUK75ZQhI9wN2LvU9TyL1cNNU9dSonvkrY3z07hLW8vykavhSd/b31YBY+7atUPTQoC76Z+yo9a2gPvMLZG707i0U+sua7vI/HHD6fx5K98lChve8JAL2KsBA9ezY+viTbkT1iCDG+PbbJvEd+vT3t4jU9Ba5CvAx7yT3cQ0++GkiLuoKdg738LJq9n3gAvvcjUL7Ss2i+YNb3u7FmCD5dPJg8PUy3vKiUdL5IVnU9TpGDPVlptL0hmTm903Q0vvCA0TwkH+88txA3PJ1OhD1XZhe9UJ1MvVXi7TuoD9U8My9JvrC1tbyTrFe+fs8YPHc0nzxOKmc8dsKavcPb0jyHcWA80PN0vYahqDv6/IC8d3g/PoI2vb32Q7O8v+B8PdKMLL3boVO9MdkdvsvSIT3ZF1U8MVzuvQnJI74Mri291EZmPTuY1DyWJPS8LcM3vpdmqj0BVUg9Ii7avaW4Hz0UFhK+XsDjvL66Tjzyip29","Q7ZkvZt67r03SsK9lCiCPQYaIb3wv0G91m4qvvYnbz2q+K292KCjPRSZOzyrqVy9UCrXvbnMvL2fjKw9j5DMPMEmNDyK82W9Fj+3PN83ir0xupm9v/JyvXqWuLz99rS9A1KVvY8V2T1Rni69S/mIOx33Cr0MIYU9oJvmvSU4Ur7lywc8lHWUvdT01L2y1dS9Pjk6vpqMCT6B/rc9zJyMPnFOHjz/ZYU9SemxPTvhxL2MOuW9ArQTvsFMmDxeWAG+DzgTvvvh/rvRbkg+U+/3vC9Yjj3NmQ0+DDP4OzLmJb1gamm+TWbcPfV7VD78jau8t4LxPfSiWb531qG+aj7LvYpFuzsggb+8mHbbvTRHLr6yv5y9YpGMPevgeT2RSVm+quVLPbAdNj33RRM+k6upvfs+M74tvXs9q+kpPfzILD0rsf+8OvPhvfsaxL3Amx0+/DsQvvyLkb3xojG9dwnKvYmUWD4Gd6I9pI+xPdy3g72gIys85riaPZcTzr1xIQ09kJOjvSRKxb0Eo/A9fPvJPJbgOL0pJ6685UQVvasCr7wHX4Q+OK83vM4xrL1FfA4+pJq0POhE7bzTpy8+IC6avRxogTyGZBI+hO8zvXargD047o88o6Hsu+e7ZzvNk1i+dCWwPKWF7r1cPxq9JsgjvYZteD3d6SO9D2lKPWUIH744mgW+W+C/vdL4Hz3bt9S9GaP7PeF5CrtMy2Q9PozvvVmg4j2SG+M7JqqgvSf+Zr1GH/G9S6Afvltf+b24Yee9R3QPPPRyAz7ePhg9otpRvFJf37zXjJy8GqyJvf/uLD3CaYc9e1wHusS3rz1jnSM+2bXEvdI51j10VZC92qFIvRt/lj3k0yq92n/wPegBD75RLeS9lzU0vWupgzwX6AA+LXfZvUC98b28sQE+NjEoPpdN7L1Chqs99f3lvYULzDzCLvo8G+evPPwwCL7MnbM9yuYfvk4l0r28VsG9TdDRPdrrG74g+5u95lnzvWTBIj2w/ve9EMZBvX2CWL6H7t+9","O452PJah9b2+18e7F1XbPPHZA70u9Fk9Thu4PNcEpL5BZz8+GSasO60ijz0ZaEE9knIVvL37Nz3TUgI+3hCEveoFy7zQeyE+2wSFvJQz87wnHQg9qvGEvT4+yz3hwRc98yRqPDijxTxaH3U9EuumPa1i6r3wnqC6yzdGPZD6Kb3sEhM+RxAmvYAMdb0cL5Q9ETT0vZeYVz3LsXk8dxEEPrfonj1qAg89oe0DvWNCST4r5kA9NnegPV7sND3azAs+OGzjPPSMx7zG2Bc93hmEvcFAT7wAgYu9JymWPVloBz7zRMo9UppCPgd5pD22IiI+wraFPXqZZz2tW4C9ppGLvS9ckz346QY9scHWPa/++LxWmy69nUIHPfkP6jxXtYE892QzO6NUAD4dlUc9HkaRPeUYzLwgGgo9lq9XvaodULxzu2w9XxuOPahi+z3OsUs9BNmgvfWVoj0o1/a8E/CyPXhF6D0W4YC9V+mFPXFPoz2/jjY+IhkfPtjHFr5JhQg+m01xvK/FqDy5A2I9CRSkPbQmczzTL5w8qTy4PUrmGz1LESy9AW6GvcdbxD0xd4U97SVKOhqhWz1nh8+8dtxxPKDsNT134XK92JWCPZVlaby1CPg86XGPvBMS6TyO8oS9dEcYvt+rJ74dZZc+baEpPWeggz1zf2W9v5C8vTrGGj6qaf07Z+z6OwmCgD2LboI9ZE+lu2Hmkzxoo2M9pei+PlWVoL3eutw9kKsDPZIn1DzXn0c+cjQfvc1fyT3Z3wo+iwvkPVs4kTsyeGy97bGePa6lvz0oTAE70LO9PZ3StT3basm9I+MGPeYLjD3irZ08/a0CPhNco72n2ZS9pVpdvHzSWD1JJCw9u7mDPQ70jj31QTk+toAePrq3Dzv4qJA8e1CgPjH2hjwxWbg7cvf+vSHTEL0wrBS+wuufO2DJED3oO5c9DB+SvUkHVz2UgOg8Z2eTvaWLPz29A7i9Aw0TvkGzrT3C4j49cZukvLHkDz4xTEK9ZS1pPUQ7tj2x0vI8","RWMlPX7YML1RjlK8mGouvUjzDD4q8i49vCwHPDOxuD1/Uds9yVq1vSd83jvpehI+uNU5PqUz8b2xAxa9eookPKygzr3hYrE9D5I/PEd9Iz20w0m9Th44OzdqCT4O5dg9Cp+1Pd9mzDw36mQ+a4OEPXoxZD0ZA5y8Bvv5vbS3oj2to14+Zos2PQz1qz3gqrk9mUguvq0EuDzSlRY92I0YPWiqjT7sYfW94cz0PEPRlb7h0Aq9it+PvYFu0z13gFK94snUvXEa7jxp9C65iKC+Okg+mrstFvw80hptvQ2uDL7cte49ZhOvvWmvrDxSxKC9uR+IPX3HFz4fI9k8AIXUvPmiDT4b/qg9xHVqva+D+L1VjCS9JOeDPatCDD4S8sm9zpBbPvCttL2UFtq9nju6PVfDE75NFlc91FG6PTv9673GbKI8lpjWvSnpQD2reAc9wMAtPps5cL2tl4g9bqGePh1LMj6D2yE+872GPWULgj3jYCk+XRwZPjhBKT7P03w9XbM3vOQxyzwZMly9hXrKPQkKpj058Hk9PdtrPI4PsLySnwk7MRUGPH9VC7yhv7I9CcqOPb6oL71JyE48HPEPvcw/77wd+AY+PI/YPUbsAL1ItJO8Y1yovPu9Gj19+a49+Qw0vX9e2b1/6+e8E4VXPp7TjTxF9ZY9s80cPrgSVj2QZQw+um1wvUh+072j5FQ9e0gsPXQloDwgfJQ9oS8BPj5vcD1mhZw93cXEPKawir2BfIc+ZWsIPGzBcb3wrlE9H40oPegDgbyUx6G8qGqNPUreij03Bms9Cfz8vKGNjr3Ynvw9Ks8qPDKoCj1uQv89KkgRPs0s5D3VTEy9+fr6PaMN+71Xs7E8RsAgPtFF9zw4zbm8wZHAPeZ3gT31tbg9E5uqvbba7b3JtLq9bdfOPELiOj6Lct89tJyLPdmLqrzAGRo9qVDquksQrbtdvqE93jPGPY+9Jb0gEsS9AH+SPWRJvj2aotw9Qv2WvRbSiT2Ly9U9ezatvC7+DT49nOg9","mmOEPSwC1j1xh+I9kZkMPnBxmbznJyq9KwrfPaVkRbtGlD4+xoHHPHXK3b1reAw+VDDwPaCKHz0rfdu9U6jEvSmeeT2WDPK8tNqUvR7R0T1zmHq8T6HxvSGH673HJAY+2pHvOxAnqT3VL8k9kpPDPRel0bzhZAi8WqTju8tANr2915Y9XEB8PfyLqDyWbg69sx8jPsd2Ej7/cwq8nCszvdPYbL1V8qU8og6jPc5kLL0JTB09+gDqPKp5/Dydm5K9V3RuvVNLfD0w2zI9Nk2uvZT6qz0OOhg90g4YvOPZCT4P9i691A2MPR4TEzxb4rc9Gh6dvWHKMDzzvXg9T30WPcfNhb3ZjWE8G6PwPVF0FT1zdZI9MwDtvX7krbsc0kw9jL0LPjEALj0ZKV488d5cvbWOH77pw4a+A1mBPCtDGj6Ut5y9+9RRPdcHXjwRwuS8l7cEvX2joL3SrFC9pRR6PBVYAL0kekA9fOIRPR/TLr0WX4E93lU9PtmCsD2fT3E9hTsSPuymGrzi8Si8ZSkEPcInBL4aDxQ7UcoOPWdpbT3RB1m9NGhMPQR1lLohYnU9FZbNvGyy0b1HS869VNuOPYEhBz43Vdk8KI82PmcuFb4y5Qo9oFpkPZBch73Ne9M86yetPaPkhz3G/b46QSFtPBMPBz4SGw4+6gQevkRKiT0Uj6I9IRqSPdJMzjwtCWA9dtIXPTrIgz2pCkG8yh2fvRXGVz6+l/49c+BWPfpIzLyWppG9UDgxPVEFLTuyqjc8truwvN146TzW0149mKspPR4T2T2zHzM+9VT0vX0Mbz2nBqW9X4ObPZW60j3MsrY9GzoHPgVS473cooA9VQ8Kvq2hFLziP6473katvU8hOjzuTQe+Jy6qvR1Arrxiowo9wKMtvTQAVT7XjqI9YryNPiWODT0+RQS+NU5GvLbXVzxRC6q9w8VRvLlloD0kSfI86vBLOmo2y73kzTQ9sP59PX53E77e2KE9+OHXvRDmIz3EDck9Vtk1PDznhDzPitO9","fTpnPRc2RTq33jg9hIzePWSmJj3Gdqm96me1PfQkMj1VWgq88zOwO1aPRr2hkXq9aNIsPS1xiL1KN8M9OuGHvcoh4z1Wlg09C/ldvl7JP72oH0s9knbVu1ZfFjt9LwE+tJsMvu77/73oCQS8iT2+vdoGQ7zDzx2++hXiPcYMlbke5e+934KhvUwwoL110uS8yMigvnI3Q74OlAk+/RQTvV0MdT6fgWq9xMmqvTGCEj3Rzre9+0nbPRMvaT7/WPE9N4R/OjGJrT1vFLi9I/EaPeZr/Lz12Y++HblEPWlrI74+S9i88cbTPUW2mryKaUK9TJSyvbtaCDxf8PC9xnG5vYjn5L3Eija8QJJWvYa4j72ZIc47xQuZOslqRr6JZdi974m6Pd3yQj1q8EK+rm8LPvISs7yGmkG9wHsXPUrRCD5fg7i9KwHBPGHmlL3QsqS7wdsMvQRzDz5dri0+eQ8GPlQwE75mMmi83/HuPRPjaD7qus08fdeFPB8dkLyVLf68FLgQvTlz8b0vc3m8pAMLPnVMaD2my329YsgMvGYGKz5hlia9Y3H2OxqHnT1KLg2+P1kovZoRbTtVK4g9QbImPh5vbTxD8UY9wzmhPZKUED70DeU9bnTrvQgvKT3yPBi8zze2vV7YBjzhmIE99jfVvbe6hj14DhW9XT7rPHqErbtc+9s9qED0vdGdor20GxA+grdVPvZI4T28gss9ozF9vMHHIj27FYo9CSBYvbKVqTtd5n69bOg8PZylOzxs40s7tqDrvYYdm73/1Qg+kPYfPh/hz706PJy9WbCxvZ8ZtTx89bG906H9O5ZHfrwHQoi9XMs2vXk3Dz2JiJ274MWwPcZqBb4XotO7R5YdvCROEj5EZFe+5eOzvC2/XL0IZRQ9HnHkvTG6Kb794XC978fSvTjWo73eHQi9z+FDPRa+hbzvZrg9DFnNPfWqr7yE2Kk9sGMfvmuzmz27AV0+J4mtPbHVV70olDc+u7cnPQvXQL54mY89h53+valhWz7h6JI9","Rb/5vZCbyzxGBMy9KWa3vSeUlbxjBVq8Y1X1vR1y9L3ZcNS8uDLUPf7LaL0mQyQ9Ea+SvSPri73ua0W99d4SvWgcPT3qXb89K92svEIdMj7BEVK8ueZYPDvMDzxni6O9h5cDPRbM5L0Tg+K9Xw35u+VE4TuIlou9hPoiPjDABb6g5A88nd8HvRf+1D1wBQC+V0OvvaqCEb1EPUW9Lqg+PAoRgj3ll7c97JfzvL2fBr4ZN7q9LUyCvHe81D1WuHU9b7fqPMMmpj19k2I8DwefPNkwlz17rke+/aJ9ve27K75mcAc+mkTqvDnDOrzqO5m7j/ynvFkajzw0tjm+c4bbvLfcrr2KUYk8afcIPUQG5L1J8uo8sTAOPAEfCL4dYWO9qDj0PJ86XD03Vt27rKWEvUUf/zyy04s9b3pfvcGvgL3/f6W9nOpcvDqcQz2JFUi9YRsQPd3lc7yWqI49KtkyvQUdWD3vRQs8SaKyvZU6wrylNiS9YAETvv4FM72jilg9TryUPTKHAT7M0Wi95LHzPA7fXb1NUj89p72Tvfbodr6SQIq96xUpPuEYgz0kqUQ9GsYkvCfsBb1398s98dcSPHwfCjwkeDg9DqOjPZDVNb4vXa893QKFvaPzxb0eEb49JOGlPUVxn77xA3W9tl8CPAQ61T2R3wi9UtpYPUFSprxrEO08BouUPIeGnr04ARY8XruMPeu8DzwXoGy+U2XGPCUJyT2X+C69r4tePc4Z5L1+te08VlAvPRk8Q72uhyM+KM8GPlg6UT0bVRS+t2qLPeLxU72obOA9K6xVvWrQEb7rjvC9BxTOPbr2lDx8XkK9kP2cPSFzOr2igQs9lRB1vS/QrbwYu7w8BTymva1CFz65Yi++nLZBvK8+xrw3icO9FPXzvfTXybypoSK9AfIePAgbmz0fT/w8K3Qpvgf0m7ukpP88C4fRvT4mJ71KSbG6hi40valudjxquYM7+IXJvZ0oEL3TuO09hmewPM/6Uj3gZZS98TvSvNj+yrwtUx68","U/QOvun5kLw+pA69RMqBvPqJRb4jq9c9QIYLPsgezL0oC0K+M0lavRZlRr2YGyY9yENePXI+YbwEEDa9BndVPReDlD2PJFA8X8FtPUdBNr6Szws+h9hXPWgONb7DcsY9VpSdvYgPib1WlWO6Ct9XPTEAvz2QExQ97zEYvaExtr3Q5xu9fF+5PLV20rzNoQe+rjesvZasvjx7kku76D4FPSu/m70Du/29yT5ZvO4aGbzN9Om9a64HvjA8kbw9nkc8iUTnPFL7ib3jzZ48qCQYPlmuZzz6rJg9KkiqO8ROO76COAq9mga/vGozjT0MHpg8zDv9veigD77TbJy9+Y9ePYvC6702Ohk+33IlvZ0spTsIo149QZRYPc41Bz5s5KI9EyHFPRke0TyIOj49DLBIPkaWtT1UG9a7dkgJPB+IPT7lstK7+Z3sPSe0mr0cwW693I8PPo5HN7weOA29trbDvIMDYbrw1UC8IRDyPY4Krbyy14s99jjMvcmVFj6i0nA9F1fFvRLkdT1orkS9UJPiPF5eKz3OAKq95kz0O80xA7z5rQ698QsOvWSVmT4dWcu9fNPAvKaekrvuXq69JRyivFGrjz0nvwM96378PfOl2rz21yA+7ZWOvaafcb0IMrQ7GiaRPDa17z0eqhm9fCNqPeEESz2v9Zs8QdDcuyqJk72qwEe91nyuu3K9xj3fbsw7Ql22vA171LwfwV88OYGnPBSLi7wVoaE95gcsvdePw73+hr09nQ8nvkQdUTwSD0S9QztdvValnjthfRe+8280PNzZOj1TLxm9Y8POvd7ErD3X+y89Ag5UPLN62LqzdWk9kjNUvI1NIT7+ara9Nay6vfhy0bw0wUi+Xh0IPGRtNT5MtEg9qMoFvvTHkb1QHMS9R0jXOVxgfj5Y8wS9oQM2vLKzi7zjJ4Y9SlcfPBjPer3AOTE9c9tfPeM0FLxhBCc7uoVLPXUxLb1JDi68B2zJPNNnQ75uDiy9FQ+TPF6+SruAmye9DRVZvuIVh70FeBS9","Ahl3vXcv9b2WlIg9xncOvjHLEr3RYmq9H1AgPB1Ic71yAQA+KMcdPCa3WDgoMs08CCP2vY2bvzpZN+e8qytyvidFgrt0SUu9BNPMOyyL7DwCToU93ACmvLj75ry5+Ia7rDwRPg3gErzWFEe9OE++PcmZYT1aAeI9Ab5VvM6unT0J5SS8lJC1PB+vIT2zstm91VUtvUV7Wb18h6084w7mvA+JxLxHuSM8BqeXvah3XTwjj4U8CveAPO3bCzwGx+W5kUoivtsEOj3eEYM8DVW0vJ9tyj1rQrC90NOrPPl5pr2sfZm8xSSevX6LqD2274u8fCBDPQcHyz1gJDy9obLQvc0eKL5MjkG96rJ+vR/r37u/8cu9X8WGPd5fub1wsxo91VwYvQ5EDz6PDMA93EWEPejfl75hKLY8vJVavUTbjrxZEI88yuawuyjxFL0bnqQ8YCgLPlbgKbwDcne9x6coPY/AML1+qi69HeysvZDLjb17x3g8L1arvFC5RD3u9UO82vyavQySz7vA3tM8xYDAvQ1v0T1xXUG9VoShvPBRDbuwa0A942FROqEIND2B/NW91e79POwnWT2dbf28BDopPQ9oWD3CIia89g9DvTKlGz77vuM73cANvSZagLtAfcC8ZcTFPCc6BrtR0bc9VECBvbX5Crx+XVS9kjSPPWax0bw/aPO9GOblOinAgD0UGYm9Cw2kPbO1P7usK0I+3s+OPBZ/Qj4zw4K+ndciPQbemT3CBzY9YBOVPd37e72dfow92WlTvYOZqb099WM+X2UXPUBwsj20rQs+AqwRvejtYj0QYuw8PsaVvZSzUL3YPso9JprtPM29+7xAzrg7f1HqPXds571ntYs9HUUoPGbblj0qHPs7tvmRPcIjyL30sjy+C0y6vJW+sb2KQ4i9A0a/PGZnJz5GJaM7gISlvexb2bxeitk8D3yvPDUUHr3Inzk9Yt2aParCBj7PpxO+sMgpvWsgGL2SSDW9TzeLu2c3Hr7GePI9QOK5PN6ypj20OzY+","IPO5vcnTyLv6IJm8tIj7PNJH+zuIXuU9v6uUveejTT6msjE9FJaQPdI+TDwL0Ei9C5wrPpjhHbwwXJe9mvfxPYD8LzwUPEo9zI3ovY/irTyvPq+9aibavHMUBr04K9+8N/bxvUa4rz0W8f29JsZjPTkeDb7ACX495OcbPbU/PzzGFd+9P5KFvWXMuj1IvXC8C82fvQbTrbwsX3A9K64rvkQ/ZT1GnaG8tHKJPU17UL5p0Lm9lk3VPSOGgDxnxbq9CFxxvYB19D2gA9M9TprBvVfQljxTq6E9oAvVPQOflD0lxwC+xHUZPnpLMT1BPVi9J5a1PXGxIz09udc8FkY4Prsb/Dw6kwk+hA1KPcD4xrxoXLc95rC4vPMcHz2DIXs+OWyjvZgt073KFAa98qKLvY0JKT1x+yo8aMGevSr9P70zwSC90mszvpJGqbyJ2ge+chF9Pd1fVr664x49TxLuPOpkBjxG/7W9AR2GvbNfgj2OVdu74M//PeuZh7xZAVY9+pe7PetF+r1tXwu9aOIyvBTcGb62820+J1FYPKlySr7QSy4+vta8vKZ9Ir2JIxc+YEhZPDmIhL3x8iq9LZY8uzHTYj1qMus9qEeEPD7LDD2R2uU7mgsmvu+Yh7xlwro8UiH7Pdd6z7yH6C+98m8aPSF3m73qIPW9b80BvChpFT2q/BW86JCcPTModjvC+We9v2FSvUPs2L3TiRq9wcUMOxaPJb2NKge9H/+xPXak6z1fvoi+nEDbvZvUMTxZTC49CVBEPa9QVD5gI2E9xsHsPSsXj7zHSLE9ALYGPZ994z0CcFI9raTlvO8q+rrnJ5m9s383PT9OoLv90Og8VS5YvanNmzuCpQc9GiUkPeKFUr1UiMC5x7gMPeBfBr1qAYS9d+EIvrKClj1xOpe9XHgSvmpPGT7PZsQ8jNYyvWfGbj0mhL89V9CwvRgIkL2e78a9x5vxPcpekL1MPX69LoHsu+d5mDyvhOc96ErOPXTCC70zVh6+5X+TPdu9XT3dXii9","31Y7vml1oT04GpO99lnduiitiT1SHYQ9VZOCvSaOXj1zXSo+XuciPAxZWj0A4ZU8UArVPIn7zbxTjSi7ZYnPPNsgxb0zzrK98+zUPVJ5tz2S6Fy8klvtPbf6ALwnTNE8BO21PYHQtb0MVDa8fKs/vKm0xj06gKk710KWPdhUrD3j4RG9JSx6vTAHlj0Vz+I8tXGhPVJ6vz2t1qg98Oq/PYzn3D0/0x286YafPcglBT30twE+vv6ZvZLSubxJgOA9zaoAPg9bib0okLw85ac0vSseGD4pHsc9EphGvWKjiL0KEOM9UyvkOy2qFT3nnaA+L92CPd0ZrD1BT52+3EmDPbY0iL0/hKM9hnNOPcXtxL1SSFG7mHMvO2iNND1S3D2+79y5PRxwKT3eqXq8k6hGvV4Eej2kQ+W9eK9WvReOfb2bASG+WZ6oPYC0O713hTA91PgKvgneuby5pTE+Jw9sPa4Jpr0Twxw8u5lUPmcehD0RBKe8TdFoPJuoVz1N8i6+XOcPvZB3/T2CE6U9Am/WPcgKDD68NOI7FgscPfxHEr7BbwC9cCgCPTrl+b2BYII9w7FnvR5gWz1v5FA++AMDPj2EHL1/a5g9Ux7vvUnqEL5Gvs27IUlzPRGrx7yszeY9wXODOWNKNjzD1FE9byT3vK685by6GSQ+NTbePW+MCj32r/29tYqCvZZJA75eywi+m6ABvV/Gg70BqK+9C1W2PcSsCry8aTY8QVdKPjd9Qj6xEQu+xWeivEXxIb0FSYy9ooC0vXqBFT7wbGU9UiEtvCh8Zr14e4w8uyQFvrmAVbxTJK08bmBuPH3/5D2n8/w9DaY0vQk74rx3VUE9N5UgPZoZwjwdVpw9PCIQPjus6L3VpG++7PBRPvMHbD0m3bG7gRDPvPG3Dz2sQyu9osAdvraM/zyUqTy8YRgjPsWokTzWZbW8quEsvpuBDj2Tgnc8EbOYPRE7Zr22fJk7UbUIvbjo5rzFzFG9CkklPNDoQzvbw1I8qpQHPrTs1Tua8wE+","+L8NPiKZiL3wZ0W9NSNsPfNWrD3x9F09woI7vVFanjwtPyq+UyUAPpETqry0Rta6j34IPqX38ry+vXK96668O/jY2D3Z2a89IXe3PZgf9bu10jo9pMKPPU3j3706CY09BxuWPQYQeD3Q6ua8NuPPPY/7TLwIy308jhtWPSRyKj2f2gk8qNaovEkuNrwJj6w8GsLcPIdG5bsX4YS82cRKPbiqGj7fvSi8DHeXvPygS70aYHc91okXPS4+D7xRnvE80dsOPphplT0afuE8iqhIPHik2z3f6a89z6MOPrPbyz3xtEg9HvqGPZwNRj2MtKO9aCexvuQ2HD5svtI9N34SPSplmz6+ble9qPIyvdOOIr57avy9hHA1vgchDD5yaL29l9oPvEq2hj4sORo80vVQvk/zLb3BuKu9EuGrup9IDL6fNIW8uds5Olz3Uz1zTiA+IiUGPu/DDT6BL5a8xqkEvqPsND0J8XA+wzn6O5/oB707hVO9kOZzO3WuoL2Zuom9J/SfPqGUvr2XCtE9AILpvFOiib4R2EA+ckS0PcG7tr1QXSS+WkVuvnak2b2Av228UxJGvRljVD1omCw++063uwEHEzxG3G694TeEPXd29jxfpcG9ENNAvgxotT7QLQg/K4rpPZcCErzJCEm9bV1Eviafab2aGcO9PV+RPocRJD3mf9Y7u8qqvZg+JLxLpwI86fGbvG7Uxj1IJpi9KpyEvQ/edTr9vwi8P5vbO/F4oT17ko47dDFFPuZZWz1pHQC+KT5hvp0a8D5mXXi+cqumvbtqgb5Bx7i9Q4WPvdLx4Lwky869AGWwvUyMDT5C7pC9mjtPvY3IG746tyw+wNdzPlTxsz28PJg+VzUbPpHPgr2WbpC9axTSPlbfJD28GuE83fL1PXuX/LzH82C+l0WsPfCra77zn8K9p17eO12+FT7Y/P68NoiCvpX2Sz2b8a0+2gsKOwVxRb59R6y9VT4qPEh9OT5rpsa+HuLSPUZVXr6n9yu9OJpPPp3x2z6rYgq+","zqkbPn7E9z1Z2+C9lewBvQmwBT2QvCe+XgpSvqDruL13t7y9zdgTPa2Fwbv8/hC/QnASP/kHhb3ONhY+AD4xPhqZW7wCjnu+LI1lvYkPLj93qMM+5kTePqVNzr18jHO9RbhsPiP1GL5qb928kPitPVytaz0fBQy+vx5pvLI9q73hj4O9lqqgPo+O3D2xQ1m9m9+yPv7M/LsOsF08fhgKPLCiPD1Cp+I9ulbNvARel74EwcQ9mg3cu/okzD2xDIY858O7u0Tj5b3Gadi8c3OSvQBPr77W2Pw+sVuuvb28SDyI7OO8LV9wvueBAL5njT8+L5sVvCIpG75tqp29ZUYTvlOH/zzxs4k8BD43PrYmjb2uxom9AuBOvqRNZT1pf8k76JgKP7T/pb2cols9NSQ/vSgjHz/rCYW9qzk2Pf5QXb3t+hy+ktSovixTJrzIcUK+4D6AvMXhDj1ExVU+IGYnPLBLAj5plYW7wntuPlXKjj2PvFO9lSAMvqUmA72BPGW96qaqPCfzFjumGQy++qzHPjG0fT72KJ28SMDHvbNPjDyaK2E+94eGvX7irL420Qw+ElbhvewLgb69qmI+1uzAvTWWrj2kfpi+1WOsPuK5W77A1dG+JTCtPdVfoL098oG+EBHRPMgGP77QyFe98gF8Pt+1rj4uuGi+8LfiPYpqtj2987m93O68PLm7d73ryrC979CIPaFBS7y4TIG9DNtJPU3olL2MO1I+Vp3PvbEOoD1WU3U9ilrmvNb4Mj1iAL09OxJKvRRk773ynze+xMPKPY1VpD39nI49RCh2vRunn71grKg9YFxyvAqM+TzVibE9DfehPGzbvbwWOTc+3XocPBHaLL34rM09nKQuvLC6CTwT1hs+LCr6PSl/Jz157LI9iIssvl8UE75xFM68NbtEPhdpxr0Pziu+H54APhGdE77Gd5U9iLhqPWF/Mz4XE549gHSLvtUgQr2ku6U937t5vahJsz21pPO8QtwLvr5aXL7Q5by9VnqpPIbanr7vgg0+","sg/UvdUAZT2Fep82FhFCPeor6zzKN4W97/M4vcRrtTxF2Os8IRUFvSAL4Dz/Pfy7DHPSvcBgGz5NYVG79CaKvQ4xIL5E+aS91O4qvnEQcr2wieg91dgRPQbum70KCmo8K9WIPWteIL2BLB2++oTDvc+nUL4rwYo9mZ3CvE8dXb2/Bw07s2kdPJCPsL1l8LC+2xVDvbhlGj5g05m8P8QRPvyvcz0thYO+VpZnvhNkl70vZ8G7d/CIvYVq7T0n8Bs+njpovaFnzz3dSR2+Q0RZvZPF3r2Kh10++WJNvZ1kA76VKSc+/VzJPEj7Fr7gibO95TooPlC3SL4XcOS+hdxcPRccEj48ckc8Vku5PSmKxLsHFYk9+takvE8NHz5XTmi+VUbtPUG8zz1z2cs94whNPbONh71qPO69yvcOPbXjAj0DtJs9G8rYvbhlyj3JW2e9VscKvixo6L0zoyS8Fw/EPT0QRr2hnJg9mSZAvW1pT703GXO97QXEPeZwVT078QG+nLsJPfTsnr11H9e8Ye5hPXYKvLwAk3Q9H77JvRwcuzz1WXk9v53Cvc1kCz4UPau9f+LsPPI/EL0un2M9ebIRvh91OL01p0m8IVFcvbAmG76+55S90McbvsAp4b3d4E8+gCtPvpwUlLgrDsu8AfSyvZitJL31oae94BeKvQUd9z0DPgW+QisAO2f2KL6225O9w48Vu6T1AL4Evg+9mljivZZBC74ylJA9yJkMvXC80L1EtmI9MFWYPVKqP7zZDu88i/tAPZ1l3j0OTYc9oARivf5bBD2WDDg8gilQvm2Gbb1Rg8I9j0tDvOpezz1GqvM8A+kHPeG2bb6UJMo9xFi8vWSumj1x6169BJl7vSZYGT5S/+09GBgPPkPB+7xbS7c9u3EJvZDNrTzz4JK6vSUIvtOIRDzAOS29+hSfPbAXDb0qw5e9TfdrPZJ1nLx9+G+99aGfPcOYhD2Sb8e9VyKovUmb2b2dOuA9atTgO2bbHr2sJUC+jVNRvJUYyj1bOHk9","lAGePWK6nT0mu4C9tdUTvuTOs72a3Hi8kHAWPVJ80zznRyG+aJ6UvdAy6rlwOEO80mwLvSh1gL0YUAq+lT/uPW2LV70ztTW79TBBvtNOZz7bGfW9W3cKviVxOz3jFVE93wbGvMFCh74anqO8ZhzYPPlJSD0Xyf+8C9nUPfkn870gRhG+dNUqOq2bVLsPcvu9pVUGvZEHPT3ReWc9w2s9vTfDJzz7/bu+1UmePZOcab0JluC9RntHPe0qAj3NQZI8qg28va4DYT3CvZ29uS/su/htMj5512y+tVESPUb2Jj2ShcU9O9aavRcQu70yRJi+mx1CvhqsHr3XJim9G7CGPn1Tqr1cGLQ9wHe/ven6QD1ECXc9RsHfO/hwYr6KqQu+ClWbvT79Ab7zmlG9lpGwPK/stL0m10i8egs0PkjfPL4eBMO9raUMPGk9Fr7f6T+9Xy8ivs5fML2LDku8H0gcvQVNDz3E1Yo9lbcpPc4THr6mfsG9BqS+vWiBs7x6Ixu+dPgPvhYoCL6E7Bk8WA4Rvf28G759DIS9yqWfPT4Adr6u2oK91B/fvekQhr44Vyg9IDQvvAis+7yHq8G8ta7IPEp1frtpRuY7ak4OvtxYF76jdKO879XLvXn/Ob7egAe+tZP8vTEOmjw5DbC+8iORvIDcjD2at22+y6ROvs+Xf7vWDa697Q6tvQmbBD3TyBW9/q78PSX3+73fCK69anHfvnT4KT5OX8e9t7/rPaFMt73f3qW9ZuGWvb+ueL3sa2m9aTBxve7nN72PHKa8RlMqvn8cgjyV8889zhCEPZoL67zWdP89YYo7vMl8871DPja8CVkdPRLVT72W4tK92/+rvYkz3rxYxAy+R7rCvViYrj2DG0++hhxMvkSLIL22HQw93ntSvkLHBb7JoQG+sVKUvYwxLT15zG29LOQ3PNGvtr39afa7haqRPQtndDoBTiE9XTRIvoLwHb439XA9YLecOZz4lL1bPgg9ES9uvSAaizzF2kQ7jmsJvX19p70j7CG9","TsG0PWlOgr3sflI8YK87vQIXQDyhxnA8UBYmPSAg6jxZe/49xpzsvCkeVr0kv6K9KmOqO+4plT0PTru9BHMVvcQWoz1vTXe9ezzkvX+3mj0oKNk9GSmAvSQ3Pb01mou9OgHnvfoiiLwyyl483/gQPsyq7bsYB2e+vzg3PR/BOL3rjB2+tjVzvC9hmbzK2YU8zNHevBrhzb2g6KY9sd0Mvsnx070xrh++ebskvd78CD3+InS7u1QYvgShS73KHgw9o4vhvVknSbo6BLm9v3c/vbPdEb2wd8c9edAQPXImir2QkXs6FsmmvDiwlL0huBo+i6QGPL7xzL0QSBG917vJvGcrdrqw7qo83ECcvheefj2NTh69UjcuvY3B0j1zJk69A1+vPmn+KD4IzQQ9YdObvfxDh7yGmmO9356OPgma9TlN59q8oATBvcSd+L1Pvem9VzJpvH2UZz6brWe97CZ/vBUe57vS/oG9uWM9vfqGoDw1to+6KFFhPMDqi7x7h7u9GJjavZvt0LzJZxu+uaE2PsdyzL3Nzns8lqEUvlY6R71TNSg9wdAhPd5Qn71HMO29D8KEvZ/Q17wLc+o733szPYEFh77GI2k783hsvayycbyA0+o8zv4rvqX0zLrn4wo8Cub6vZrAb71BuIA8i0Vkvo6ppz0f/6886e82PAiqRr2pTXU9QpYiPm5Diz2pIzW9UxRtvHuzKL1fML0+mE2DvYShr73hRgC+/AkZvD71pD1DWc067hyVu1UYSj2Tbo290WnSu3ySsb1CKtg8XmaMO3hkrb3Xrkc8WWH7u6Ln+7wU2wG9D/EfvvhxsL3Bc9W9bO4FP6/djz12AQo+eSnPvXX80D2lZ6u9gWV7PlVtnj1Y3mI+dtyTvbkTk70/Mmk+EaGsPnIBNT50doG823nxvC7g1b2/h9E9e4XnvV8N0L0mbju9EcOqPLx1Dj4leKm7t9iSvXT9aL36ZNo9WyvGvezC7L1lSYK+f0VPPulAprz85Mo9C3QBPQortD15ilO+","MQ49vhMaYj56Yxg9bjSqPaSRkD052Ru+adjAvOIxIj20v7y9L5ZXPbgJxL1gvwu81MeEPRKIZr4ZMqE8L/mou+OkCT04x/K9GTeSvhxHq72k15u8QSxTPStz5b1h42s93nPcPNCwsT1a5P29jGpjPQ5OhL0/zII+R9GLPcRMabqRqjs9wehvPHprqr1O7949lnqxPoV1br2Pzqu9Z8GGvaABrT2xZxw8L6+0vRLqlb3Aqto9TL6fPAHpDLwc0hc+M3xCvuqvNL0hrlW9VNiqvZ+un7wYACm+WbyuvKNRzL07D4A9/f4RPaBVbj0gBxu9c1kGPmc0+TyS4ps8E5DSvXWQ/LvXXwG9GBUPvpakAz69Twk9aHahvpxGpr2IwiQ9iTqivUmOo73A3/W9WMkUPgL+KD6h4hI+0MWnvCO0dLwzSQi+a5dlvrtLND1YiPy9UD68PPzM2TzPr7S8uiwBP0V7VL1B7fA8MBeVPe1uib1oVa25npbxvDUn0T2QrHs9NZuVPWGIHr3cKnI8IUA5vl9qkz39hFq7hPYlPGY14b3M7OG93BZYPgL7yL1ZyYG963BJPlXaGjxzyyI9db9OPXJorz3+WHS9VjK6vPgsmb22i3a96EEAvhEJLb6Z4q48GDXUPAY5dT2ztdC9CHIbvvnwI7678ww9ieqUvLwKlD2cIdK9JwebvK6JnT1IIym+/BH5PMEAAr0i8Su+mH1APFGuGT62kHc9uaPlu2HQGr1oxLI9WIbmvChltjqEjhG+k508PR/TzryPY609CUsnPb1UH74W/889Xe87veOl7r27qT+9NL6TvNzfrzs/VWS9UuMSOzfzo7waPge+2nw1vU/UAb6cu+o86+kZPQMkJzxWnI698djVvRl4KL0Njxw+XJOJu+TrVb0Bd6q8dktju4eYPr020Z2942NwvaghSr0W7iA+c3svvVDq0b1KNSw7LVG+vY9o0DyyMRc881KnvCW1KTtC5jg9BMvFvcgenL1nkaO8I+swPfXdgj3aAlq9","vGPLPWWKML2fWwu+NbCJve3dNLzA39q7wUgUPSvh9zroIQQ9yQsEPvMbu72TBqS99QjUvXb5mD1gSbW9eTVCvQbdvD0i08m9soCKPdUYY7yn0m6+vLjuPK5zGj6aE7S9+BSLvfX6CT4wTCM+ylgquvE0F75SzNw9fq49PLFatb10xlK8XIN0vRGrgT0Rshe+klHbvVOPj72amAI+mexfOTYkIL4pKbG8zIDCvQJhKj0QPaA87okrOx8nAL5E6Ue+N5ChvaOHsTy+DdK9C6GOvaPWrLxrnoA6N+avPFcMbbrVko29HZhYPqAGFz1ie448/DqKvbH34j0uUTE+CjdjvJLJRj6rg7G9h00WPFOGwD3DbmW936OqPZgQ870/tWW9W9llPH8Wn70/tM69MWwSPJFMx71JjBg+wk7VuoPTUL7Qyea84L+dvVEwoj2LND4+k3/ePNzEz73ewgu8QeTIvdScP71L+Y+8hiApug3vUD2hCMK9KcWkvCImxbwM52S9aUTOvAnewj3HLA69Hl9iPdgiKL3/0yC8qI/DvHnylz0v44u7TezCOpxCij0I2ta5Xn3FPNNcZT1FIIU6eRSIvNW9mL1ibgI+fZe3PGaAi71BfL074eH5vFJ3ST0ptCq+gqoqvBGBgDzNsJA9fDEtvtbgs7x5+t49ufVVPatYnL0xIc29HX7GvO2yUj0wkJg9b30Dvfvsbz68E829lF/YPTOiQb33VPI8gOpaPfjMyL36Gsg9P5m9vDihFL1xqnU6vdnXvHIArD3x+tI8dMvavfv2+DyCi2Q9JGIrPJeQub2HmVw9ZW6WPY7uEb6ADNi9aq2RvY7Sqj1dyYe8deL7vYuZ3b1IAGU9DiouvQNAX70SMyk+T50yva9Zgz3p/k+90Z96vc+zFr63dWA9BHQAuVx5sT2WY7I93UZ+PXNdtr2DBJi9LiiGvTHoxLvyA946T3qYvVl/Kz2pPwu8GeD9PN0oqrymsUY8avU1PTEqmL29Mu69utWDPW2RFr6Yu969","eiChPf8+BTxd1ng9Z7AzPnGDtL1+PHK9TXZxuRgKKb6BkZC93u44vZvbmr1AY0S9HjE9vafgGrxn7Fe9m2cpPjuh/DyraA++MwbGvc0hzb3SIkc8QP4RvlQgYT3nOUQ9VBACvfOg5Tn1ws49Q1ZEvU7y+L0LIow9VYanPY1mrbm1Csa95RjpPYZh0jyh9ha+1ElYPoOX5juI47S8SMHBPYTVFr7Om5u9NLiQvVbEmT0TQ7i9cxvnPA2BfboPQRw9xpluPmisFr4t56G8vOmUvIs0GL1vvTK99GU+vXxuWr2NCi6+8ckOvYcxxD2sjtS9TcKLvdbvhT3c93S9iT/yvSPHszztZg++El/6PLKuub3aLGa9OZjjvFhCzbXdOG89PrYdvcswK72bu/u63kPEvA9TDL6y8Jy9T1NSvQphR71XPlc9wpPNvaAPYb0xWpm9Nc9wvdc4TLsO0CY+YFAJPjTrrj0XYdC7B/kNvfj0Qz3RdbY8yLS3PP49kz21kSM8H8KWPHOimz16n3y+cehOPrAD0r0C4tk9HniIPXCMDz7IRCg9teBWvgn98bxyawq+MAsMvrQu27ulOFG+qCjIvUneZzz2YtE9mv1OPgFhyrxQKf49F5D5vUoa1jxvw6890cnVPSSFKD7XbpM9qKVKPaLh+L247zC+Adx9PcFG7jvV19u80AQFPcVKnT1+0xs9Ejm7vaU9WL6gakQ+PKDgvhzfWzvuuNe9n/zMPVAw9z32BRM+8EUNPegzU72Jqfq9vDAgvSMVtb1yl049wMOOPf8p6T3u5g49mgfhPQevJT03NQ8965WnvUBz3Tybg6u918BevH4+o71loLS9GuTXPMT6qb0QQbw7nbLyvVo6B74k9CQ+Gr/xvU+wPj2fjdW9H0oPvvSFIr2JxX29AZzlvTrTxL3IqKI7NZ2fvYU1k70Gqys8SRueve5UZD2Khy28UPZlPvehAD1kCso7CNB/Pboht71DZ0M80emXvawch70Bd4Y9kNy4vdTe4LyG4Mu9","ATQ4PXuh3T0N9pe9WnEDvdPAur38IH69dMZuOzUsFT2rnBW+rSOxO+fPOD1RK/88nVGbvmKC6j1ql1u8zK/uvUa1Ab6LP/U9xEsMPHkQRjxC+767eqHxvH7NHj321yI+NwLcvJf0Q7wWSVW+L9xfvbm7UD33EK691w21PAGhgDzsKrk9rTPfvBabn7vZ+ts91vX1vQsEC7178BW95mdRvXKYvzp6YyG9y+bKPBftKD0jUVq8ADzIPYGTXj2jKQs9Mh4xvZtWy7vLrTq+kszEvTaw6r1whPU96aW/vYfch7x+Eqe87uXjPTg6iL0ppic98ye6PbJ+x72Cel48w3ORPGpjHL24nhG9t4KDPStb3zuLXLY95IEcPV5WHb5vcMA9nTJBPUIcubqtA/e7AunwPZBrE76RE3G9Cfowvg3MST0s6Z+8tgvPPRqEFjrG3ay+VPezvd6rAL1YyZi9AhaTPFGwtLyScKi9NFc+PTiV6729dx69cGTcvTE8mL1xTp28GYk1vhi0h7t9HwK9k6vJu1Hg9z1t1Um8YEcsuz/zHDzQKOU9BdiKvX7aID0i7gq9l8+PPY+Vvb1zZza914GhPaQ+KD43iWe8uh7uPJsmhjyIU18+voJBPpnkXb2U02K9U0oKPumGeD22Gzi9Jk64PmvmbTtuuBM96v3/vWKn6j3g7ha9ePeNvfeVkT2CaM67v5iCvZSORLz2mLu8UipDPoSF1TjnKCA++ewWvsEIhzzs3gU9VskWPcBlfT2hoTm9d5drvnNrDD13Uws+YVinPSCbvz30giQ8izbNPJWFgDwbGnu78sQvvdltkr16FTG9AG6NPGJEbT30Oqu9tY9Qvsqppr1miya9FpWLvgjQMr21BQg+Xs/DvWg6/j1KpS6+bUdUvX24Sb1887Y8w3PsPUUuGrxh1Be+cTGBu+jw2b1FS2+7+whoPQKmXL7h/Xm+qr8FPukZgT3sy8Y9bM0svv8OZr4Haia+bfqbPrIumj27Lgy+a8PVPQSd1j6BeME9","qdOWvopGA77EeLw98+xxvLbFSr30HgK9uOGBvdFBLr2w5Vi80byyvarv3T3AOSY8bG56vfKcAz4RXtK8peKnvALARzxQN4Q91qY7vtM5IrxAbmO+10pSPtLE5z0tmcA9RokTvhsGIj447j49syGMvJvWxTx9eQq9Gi5QPFmym7xCZbe9tkKzPXGdDj3KZBe8vSAAvmHNYT2iPIW8ko3Mu2fmDD7UMUs9jbAZu5VvPzxNS7Q8BSzGPS/vAzwhQlk9mAntvVUsFT3pKt+8KRG6vP3GFD4k2FW+7NP7vXbdbj1DExM9jakLvaCpyj1fGcc9ZDTjPbcaEz5yxE68p+hUPLVROj1E34c96U4XvsuaND1QCu89/r2gPX+zgT3Dcaw9Vbm3vU0Hvj1OWuW9cpAbPkcoTjwKrh081eSFPU6rDDzEj6Q93p0BvpVeoL2TG629UVJWPloXnD1I7W+9AoeCPRitY73946m9aD/YvN2cTj67IBW9BVWHPbOOw7vkZOC9r99HvMatVLszyh69bBqzvZQ8Bb1c4NU89p88PWMnhD2v2p68c3rMPIhchr1nTM08OHBBvruWxr1In8O9T8HhPGKu/D0MbDY9borjvY998zyfica8FeYivfk7az2EJKK7/3H2PCFV8L2iWOs8a1ZyvUxgdL68/Ao9k/sBvol/sbx+bxw+FQafvSWh7TyDv2M9ZNO0PCOBoL1B1bi9oHwGPUL3Cr4QkuA9RffovUUoQL2iXL89blESvaV7DT4nBxg8uGjxvPE24L3tj6o9sbifPtcmP72LTP29mARDvEk2vz35MPI9CsSHPY/rBTxCYVY9VBeOPdzyML3SL7m8iGgcPhqfFzzjihE970IwPbe2w70YAZQ9vv4KO5Fw7DxyJjA+xBfivI3FQr2k7aa91wbyPdqSoj016Jo9TFY0vZEUAT001h29mA1qvYc8p71a0K68TsK2PRo9TT3QijI+013BPZX1Wj05t5Y9oD9AuhrwjD56Jis8NvtqvZlSuzxB5Rw+","hoXKPSNR0r2j5Ii8OCzxvc7rszv8LCs91/gdPU7SWj4X5o08bKW5PVQJBD3YT/Y9tQjWuvbyAD5e9wa9I+HGPIvyiL1Z1h49l0iFvZgbxrv8O5G9LChcPZC5hroK6p09qw6qPPhkKT4xoCM9acdNPSO3Dr6WkNU7m77bvdI0QT3jobG9uie8Pbj8nT1njvg8CfotPBW5hD1c/CY+bhsYPg2xkT0eQRE+dO3tuknQJD5fh5w9XHcBvSNI2TxTdz89Fs9OveNfhrwHGcw9jpi0Pazj3r178yA8UP0JvlPorDpkxgi8yD0avs18hT2fIIA92ZFNPfBerj2IOgg+2QVVPb7iDz4SiSe8G+y0vM+phL3s1DY7iZ1svVLNPD4AJNi8BnrUPcaDLD0OeAy+kMmhPAo5gjv+F+K9INKePeuT7T2LEI49RNgVvQBDLL1Rb+W98DgxvhU5gj0B9pE9+M96u4ju8b1GDq89hzEFPcGou7zn8c09L1xWPGP6EL3WQRy9upmZPVeKE74LUl885f/jvByUIb6yOyA+ULFYvAGwyr1QFva9a1YnPe5yY7044JQ9Z+WjvKrsNruapdA81sRfPYAGq704zWI9CE2Uu9oXu73sPBM9K69SPZG9zjxqsQA9gfOVPf+nyzwyKe88AszcPby0WL2Ybw+9iYChuvWShz13LkM+fdWgvbYGbrylfNi8BCjvvbsUBbywPrk9fxvMPUNwrT0khoW9+TEPvfwCxj3AYgU+vvNIvrEKr7zVyEe8AppRPXB+5j0FibG7tH3LPQxacD3ucOu9491hPc4rlD1W1uQ87cBKvdod4D1ftAc9ePdkvZYrC7uR3wU9HOImPcAWtj3VEvc8rVdTvT/hrTzmeEK+7RmAPVxknr0L15K9ME1LPs3A9jvNmdQ9+XE4vhD6zD0KDQa+xXQfPtY7tr307Kq9ULgwPbWDMb6jdgw+Bzw2Ppf/HTxf7Ri98PIbPZ+4GD3ZbjG9kQeUvFHs1Tx0MKY96lRSPYMouTyvb1g9","QCNTO85AND1BpUm9Uf1hvdrrcb1kYYM9WUyvPos6er32wiE9IY4JPZl7AT0JNhI+KO+rPcY+Pruhsxu9N9OfPThs4b1Lew4+QiM1vg9c0r21Yso9IofMPQiQRTxlvGm9JbiJvTJRAb6aMYU8uyPjvZVjtD2a5pG9x08cPvBtvbxMTUC9r5eYvYijJbwul6M9AkzduxPyxD1DdVu9VocMvsKSXr17SVk+yIbLu98xAj0u2P08U1jsO2hNU70yzE49ul3BvTcd1j2O1r87dbwVPd+YML7cBjG9LxbKPc1sE732QxI+ojMVvtJzoD2Rcwo9LwQ2PQZI0rug6TY9Uq2QvqPsIz5KLZG9w+p/vVnQiTzX43o94yyGOaiknb0A7YA8HcTGvVILuT31Sp89j2DAvYo9Xj6QVQE9gBiFPEbTSL00mpY9diBZvEyGWj66fqu96iiXPXu7aL7g5e28VRL1vKfY6rwZOs498Tiuvahh8bwRq1a8O2tGvHtYebtP/6U9dHq1PX94rLxhbJq977maPBSxar7YnU4+f3zgPCkpYbu8/8U860dYvsYZ37zdwPa8kLK3vUOk0D3p6GQ8F9AMvQ1Rfjy5k509vAUYvoWDB7x5Wag9XIXjvG6CrD0gK4g8EnR4vTUtOj6Ow7q7v6WFPaCc0bw3ph++UW1nvlqwFj0+xNi8CvVNPQ/M9D3Ty+I93M2AvGQ7WDzyrx09VKC/vaK21T3pxx89XzqSPR3lCz4vz4S95pjLvUQ5QLvrJee96ZPkPeVMwT0KGQ++IduyvPm+az4mx7E9Dt/xveIisb0X2Ea9rTbWPWUTIj5KdBw+kmq5vRXOcD30Opa9bpNUPIVhTr33tfi9LTsIPaoftL1630c9yYxMPavokD3vuMK8EWZpvirDmb38+Uu9l3ixu3Vmebv25U294xrgvA2JSL30a7W86q5dPQthXzydDcY9+eABvvleHr58Rhe9uZwEPSb/vb0FnN098mFNPA2TN71fDb89vjPmPfF4aru3BEk9","yiivvHH6rz3yPSO97tOTu0P6/r26zEW+ygrYu0Cdtr3UQEq9Nwa3vB2XUr1n2Za9i4fEO8+k9zs+JSo90eFMPfSHCr4l79C9twQ2ve9Z8b0pRvE87zjlvOJVXL2wmR8+mbfmvSaiQ72j3oi9UG+bvfkNybqHfaK9NK3+PNKCyz0ulwA9ulaOuxqKer0wNZW9IKf9PElT5zwy0HA9qjMpPf3GozwysRg+bYNqvbzjwT1YVcG9xgxTPhGoWbzxCJs8Z2F7uy1DZbxyQci9uhKCvTYC6L0vpDq97YENvqJv67rDfpO9cw2avV+ooD2lveQ9HoAUvcvHsz1B/8U9cNLqO28HrD1mu9E9C0k2vuV9S77vAQe+C8quPdxx172F0gs+iroYPlRwiL2I8jU95cjJvefKeb2SE1o8KUdyPcWNhz3tUBe9Vr0hPOGSFj2Rp2O/kIEgvaRDXT20QQC+fajUPRvdUL2SKK086syqPUd0DbzxNqw9HaSavYwj3DuN4Q8+/1ZlvUX+Kr0zkq6+/B6NvLG9pr29kow6rbyvPa3XmL3vN0Y+ElCHOu3dLj2SUwe+4B8DPu+/or7h77K74CcFPFabtD50Fpk9k9RQPJ6FqL2rytY9Be8rvVoZOb7oRH890Lsovs+Zlz2ivZi+j5LtvWM9Yj7O870+emkCvd87GbxpxMW+WiTAPe1aSr0hKOc9NP2NvXE7jb6XnLG9x2w1vj5fyz11brA9YfixvWMEJz3sB+M9f0V9Pca9fbz3lCQ+cTrEPXXRAj4q57m86yi2vlX0JryM1YY973QkvFUfMz2nAAk++V5yvJ5iyL0HaHs+Fj2FvdRqM76FTIy+lRZXvupfJr7pNQI+bZySPZDtYj5D4a88fI8LPV57A73AWsq9HCXTPHuF7z2r7C+9VKS2PSMrZjw9/gM+vxK2vvE5Xb37HzA9PeASvndATT7IB4k8SpCVPcyrEL65P6A9MhVTvg+i/DuGxmA+IqxsvtNeqD6zrH49zIecvtVN7T1f5YS7","bappvvEaCb4fKvg9Yh/0PU+I2DwB4tE9hNA5Po7XETzR7LQ91rqyvQpo9zyuehS++B+VPkFKrTxquNw9TeUpvehLIz5iZj++aBMMPVpdrD03/Xw+n7L4vSVD8D0BNOs9Jf2jvQw73rwjsJw+uTUiPn164L2wnGQ+4uyNPDE8qT00Qgg+XIwuPod00T3gTFs9GMHnvIqr4LpKul++BBsGPgEtIz0NIAc8H1yovFQsCz63kdC8197TPIOK7T18Ztu8uJEDPpgxHL2uPKC+uCodPlAUdD14aI09WLfMPqkOML+KD0Y+IES0PufiYL1v0Lq9b6wTPYj+bL07SEy9nfXIPWkEgz4av18++ueZPbE15rt0n3g+dYzEPotemjxhViA90xH7PHKWHruUIQM8VJezPVWPvbyyPtK82B3sPq9VZzypqNI8MLf7vB07573IQLO9HjhSvTdskL2NY1m9m/pIvYkiVz0AM2g+zT8ZvrR4Rr4R+Fg+Y3+UvAXXez3EbHC+2hK3PZIaOrxHz8U87ZEwva9i1L2bqVU9qLTtPUbwLz1t2iE+cp2YvTXHmb6AEiq+Y80NPqWVDz48FBI+qR6/PTQtxb2zhcc9ny64vPiuDr5O5dQ9HjduvBsdPz6HAqC9RDOBPtEgqD34MQy9DgTWPcEcFj1WoU89l1diveluND08orI95r7ivcZty70vd9u9Dzc6vUmJlD11MBc+v/KIvk0aXj2VY709HF/EvELOvTyx4QY+lhgAPkSWqb3EIii+b7x9Pdasbj5yddM9Hl0hvv9oQDslWx69NA+BPAl5+L2S/Zi9Pt+gvdPeEb5ArDk7IKVqvtZx8zzx/su90w+sPTiEuT2Gp3Y9pYlwPQh9Jr0qXg494gwPPVJwuL1VhFo9SS+BvbvOMb2ogSy7gtMQPE5qEb4rAxY9WD7BveJmZr3h6nw8nxctPZoNhrxv0Ik8kAPPvTDw6Dw2OjA9y2CNvU3+5T0ajbu8tUJovCUv/73+lwm+FgINPeglBD5WQqC+","iuFqvWlXBb7zV0Q9Ps8FPqAw3DxWbmS9CWPNvQQu0j0Nj1S9BJIrPoDqt7vOuJa9u0y4vIBKJz21qMC9MYuUPC0OI70cjSY9Ry/gvWgW3zucv/Q95fNbvOUghL7nfku9hBa6PegOCb7aPgK9SrKLPTl54L2bQaa9+rq+PVVuPz4rKYM9O69AveNsBL75HQG96emJvllhbD6PAjy+EA3WPZ4OIz7co2W9wewGvvo+iTxlnpG9DxAqPAQMWT1ye4U96f2kPWoAaj3Jn6+7N/5fPu64QLth3cW9Y5RqPfWEiDwbTgq9oHEvvibDEj7GNDa+wqXivSXsFz4vxDA91UINvvt9jz1FbBw9iVhiO9peoDx70tq8NJcPue0UH715Pqe++WIxvotfAL49U227m6xsvh3zAT2fWUy91fHhPQ0PkD1d5Uy8WCwHvin2DbsNZd49B+8FO7/m1DwTsAc+M16VvHKkUL71bk485E6Buy8p+Tx1Ng88tKvOPWAonb1F/hW9N6iDPYUE3by/xB4+x8vaPET1yz1636I8ltrIvcHyTj1e9si+qfTcPZSwQj2T9TM+il5QvP1YOr2y+Wu+tfwDPezkKr6lso09ayIvvfmBQ7unL0G+hThMPctGj7weehu+yoESvqUN/ry7pQS9NnvJvTpylr23ZpK8yhGIPS9lET7BVHo9ImOwPdefPr05NQU9hxeSvSbWLr1JDiW+D8mTvhlRDj5EaYe9Qwb2ur+aET5PW0a9CtvfvWf5FLuNFE69x6PfO1TmEzy15Te+k2GIvlW8P7yhntK9CStmPYEVxDpcTDM9TOGjvIIU67txhEm9UKtmPRpjyb1q5QG+mHNqvQvsJL0+KQI9mzxAvc7Pjjyhvmu+LcNgPXLJdjw6r269rv2qPWxEvbxVA8E8LpWMvtrs6L0vr5k98FOyvQSdhDusugO+80Osu4O7D72sNck9NOYxvjuMgTulwls9714gvgUJFr1iT1y93LuGPaMuCr2gWSI+Bm6tPRswy7zreIE7","3CgCPgFIPT3OhDU9ZsOZvf2a5DxdVkY9GjmTPo2+gT3HiEE8NGDmu4+BLr2AUUK8mYzMPVeubT1tY7i9OBL6vNmGxz1k+ck8JQzovaYxRr7pUzu8yqeIvcvaFbqGNG+9HmpIPYXJXz4qjn29c33UPNul9j2Iugg+mBguPtb4aDv4Kae8r4YCvFk7jTzBLZW7bD2tvNA5pDzNCYe9aRA9vmD40b1J0cu9hQLQPdU4Ez7jRO49x7m6PXMRwb1U3eE7gUiRPjfGdL2jBSE+Xs1NvdANsr0dWzG9unOZvQHpmD347mA9Q1VMvaYwk7wfYwq+HRn7PbZqtj2Irx0+3aEKveMzvL3EUSe9g8SSvcaOs7zeUhu7IbZCvWlnqTvjuJE9eVGWPT5unj1uNpW9G6LjPaWZCD5XEzC9qS46PIU9rD234t69unHEvaoCKL5Xkey96omgvTiTNrvrhuA9MloHPpS5MTy1sGC9PESevFtFFr2vpAY9nuNIvf87rr0DiTs+Rf5LPQv3lrwOcDs9xaaSPagRyT0as4C700Y6voW4or1OG0+9YcSzPQvXNr0cv9I9E93GPZ8SHT0gf6c9GxScPA470b2Uo9e96XHrPBzctD37MJU9jeYyPayExL1q7049ro8PPvgVeD3U4Na9g63oPB10pb12wWA8JT2PPT5b3rw1hcs9c/Eovo/BZzwLrUC9ghIyPU+Plz34GcK+Rrs8PgD9Hb7LDbE84I/6PbHiRr2MP487EI4IPdyh2Dzn3pU71hdwvU2lIj62A/I9DJ8DvdJRNT7nGbS9jybdPHbVtz3SGdy93L16vHzmFr3lsyu81vh5POn/tTsZIds8BeT+u5NKhzyLSBO8C017PZQMuby3S/O81gr6PL1ILD2SlSe9tUsNvUCR6LtnM2G9UuOsvVsK8j04t6o8HLQhPQu1p720lZy67r4VvNJQ57zUYqM8arqlPbZqKb19hBG8c2EzvvBZf72lr9c9uZgcvaKB/72QIlQ9ovnoPY5zo73Rw4W9","Yp/qPSJ17Do51508sVDevUhv/DxqZqm7e65/vdxOXjwHP+M9rE8UvVyHxj3nlSI9cDyQva0ZCr4mTdg9dBrFvVXOTr2DZfu9zbzrPXULMr71GXE7j+P1PSc+tT3+CeS93oznvZIgvjwEg9O6R0uBPSgYm730I0a7zjynPVtYR70f07s9UzDiPMGy2T24/Kw8i0+3Pe4Whz2ut5C9i/rLPZbpWz2cxjs8zxs6vHqdGL5+a4k9fF5OPcfXez2a4By9aR7nvDxHvr2ePh2+CrduvcFlLztI8n28a7y+PTKrE70E5Fu8v/E1vHmyrD3A42c+UoF6PpzjyD2ncVC9n4UQPtpNBb4Ub+29zHsRve0sfb1qglC8UKG1PfXQxb0LCZU82p8+vWwSdzzLfR++E6vVPMLv2T1mdYk9GVsBPcmqQT01c9s8tzYHveaWZr4lH2M+Cd1tPd6oyT2fMZs95L2ePSHMIr2rv/O9YMRJPODiCjws2Oa87kt+vK/UvT3z/ru9yKUGPTuaKrxWyom89RGFvZLjCr4c+w09068LPsFEsrwP2bi8GCptu1JoYz0lvA09lAbEu3Ia3j0uaSc+weCDPdntGL41Xvu9b956PdnDzruio24+AiqlvVlYsT0SC9I8OWT6PSAmBr2sw5S7GsmsvXKSLr06lqm97qH2PbqPyD22C6W94wzHPVcitrwE6da9qYCSvLVOE70cWOc808PyvaxQEb3vBTq9J0euvd830r3cggg+dxZRPT/B272/7q27F2PGvd1vML2++Ey+YJ0vvWDO9TpWkrA9enA9vjSTJL2N+r+8dTIOvcubAT7t0Yg8L+aivdqM77yjwcu92ctsue/C7TzaLxU+l1xuPfBi3Lpe/449RX6kPfMNl7zUGr89QtfqPArv373SohC+ClqjvYjOSL6hTKQ99+ynPZ4O1T3qu0E9O87qvDnzRb4Xau08YITVO+WvkryAktA7RWgwvvMRej0zHwm+EleuPZL4BL49Nnm+U75Rvn0RUr5Rug28","3m5LPgSqbr6dN8g9bzm8vU3AiL2apm+751m6Pbi/Gz5LcQC+1bvzveCkhD3lgVs9zoiXvQeRZTtTKpk9IhgrPTPUTTwSdCe+sQNTO2b5Aj7ybYy9vaFRPUr77T3Eyxi8YAVXvXERxz3hAXu93TbiPHulkb3TKSK9Wy22vGBRKL2k6QS+g1YLvr+iCL1shjy+HHT9PKPagzzuTIi9Re6vPSYKj777b/U8nWKXPCgoUL0Wow8+Z6Y+vh2dKb644G49x2GBPet2mT0GQqy9gxKQPexNOTzdX6k9Qs8LvjVK2L1wCqM9EUMXviJNMzxAHRy+OTqzveqQgb0rGl+9tMyLPAl2mz3sHsO9nP5BvoPfLb2oZOE8XqQkPpUBiz28qd29zKp4vZoDmT3U+c+9H2KAPWssRb3CepE9V5k+vvMMaz3ZHJQ9Z6ohvd1rtjy8NjG+ffvBPUaJ3r2EYp49AnevvaD2170AD0e7VdXMvZuqET7mzJC882quPcrWbb1EjAu91hcRvi80LTxtRwe+rj1FPY3brz3GE8a9bdMHO6g2qL1jM++63ZXnPeiNx73Tivi9N/PlvfothT2tvAo+Z2wXPVY//zy29Y49yaGqPd2J9L01Qks9tkoAPZVzAr7I3vc7lpqivSxEBT6yp7G9qdKkveHXtb0wAwu9OMl2PVcSHz60l4A+IhoqvoyA3T3j7zA+m56kvKjEJL7DDB69tb+YO/0kMj6aQ4G9+dr2vQ9i0j24HbC9oQlnPWq4rryLdzI+YYEEPi+YL7zqa5M9sm6avQ0EljtimJE9s4uMO2N2yDqpEso7SYAVPkh/Iz1yy4a7nlnBvbbhCj2/GqW9M3HavcPQiz48BFG8bOaDvWSl5LwFy/O9lW1NvVbFAb7G7AY9Bt/avL9lFD5vbP09jjBMvuUwHz1QPM08Q+vyvbWS2r3bKIY9ZebBvTGE7DzXU/89UX91Pv38rL2UAJc8/1+OPqsFwb186ye+OuBhvdHjJr0KhcY8DxKLvMBifD4YIpa9","cID8vMnjjz1Iz9c8TMQrvQiEl71pZmu9DiSvvbM5Cr7lpbU8+/uDPVYGfzzDySM9LDiYPUzlFr7OkyE9ICmvvKgrRD5VBQw+Yjl1vk7A1zxr9a49LKgVPW1PDD0cwwK8Tkl3vRvRSL1ApS29E15rPELGZL1DWbE9YioSvpgs/T2i8be80j4zPL762bwkPDU+Gc9WPiS7gj2e46y9Axm+PNcvF739n0Q+BuWHPW7h5L0PVGo52CWePYIT3rzxpeG9SI9lvQwVfrw8mK+9VlANPiY9MT7gsrU9GP6EPUa1kj3wDog+XKsjPqWyg74Xf8W9qzECvo0PfT5FGjW+vb+Svcesn769Vj484CbfvE6GPb5H0dW9hdMnPljaej02e5I9t9q8PU1+672f0jw+71O8PeKCGj4jNAs+6n3cPaaS+73+yaW848GFvthnWj2oPyQ+dDvbPaBlI74s2R+9k69lvZyivTvkzH290aq6Pd/ahzxVUYC9a9ZgvbmaEL4p+Lc91YPKvdCDCjsmU9U9EcmzPFWUOz6tlUC9KnQXvrYxmjs51gI+orievUrq/DyIWD692v7GvbB0jTpap/a8hKxMPS9zkbzFpHk8ap5xvKoHi7tI1K28SpyNPjfy9b0xlNa9av2lvPXKmr2WA4u84ahNvgZyeLvCvBU9Ig4xve3OBb3P514+ognbverhCD6mElw9iNj1Pfya6z1CZm88Td4LO9yOcj7AglY9irppPsnkx72quhm+uZOovZvjhT01uey9KI+vuyZM4z3ynIC9hO3vvIejHb4EiYA9u3+SPj3CvzxCEVk9hGWvO+He6z1osUq+a6hGvZZb6r1m5e69B2DrvRmmFL6tRAe9xJ/RPfjYA7w+blQ+023yuxFD2bs8poA9Ik8UuzMHFT11e8g7JGRFPlIt7T2byNc8bd2CvsKRkz0XkoI98FGWu6Z9iD7uG+E9cXk8PQYvib3xOXW+DezQPYDwQTt8dNC99tskvRZ6ET7Aw5g+w96YPbNwmD3Emeq8","GTEjvZwK3r07gIO9L9n5vfo6Cr7bZTs+n8UZvdJXmL7Iiii95noDPqLCVz1kxWe9+GDSver6XLyK/3k9MWB4PQoKwL1HSpw+YzT4PoXPWr7WLne9nmyrPcAuAj5ow8A9yIu6POZ1xj1vGei8CP65Pd7VYL7w8M48IeXJPdDP0L0RHQ09H3aCPMhEzrz/cgK8S3Eqvg8xEz6NvQW+Ezb3PQFMDL6qY2c+cBb9vcJVKj615JO8cp+HvS/jFz2Oypc9JLSBPsvNqz1Nj4M8SZfRPXFusb0mqmQ9fJUMPhtuzL1buFS+/SRuvt5/vj0atpg+ygLOPbJPVb1DrqK8CBfmPWWsAD4xlA69DtK8vV+bwT0wHH29ARZnPRJNLL6QrMq9BNsKvQzhcTy/T7U885MJvnsmDD5Tm4I9qpkxPkPdW70ACUg8+YvgvAfvEz42Q4a9rzcQvSpbkr1IMe88LqNKvVtdaL3c8Sk9iXoEvkPaWr0PhTk9xApMvDX9Ab2h7wC++mW9PNy1n70HXvU9vH86PsIELj390QQ+5M3TvUFBEL7/Jp89RfvQvSJl8r3M9Oy93vYaPhP3D73wUU298FORPQpX8rwLWe+73Y6ZPU7o1z0UCEO9HdKxPV1HBb0PCTO+grMzPnxjLL0wGJO99QiMOoMZt71Izfk94y6zPvwhY74T39s9ufyzPdtQV71IrIM94t+CvJNjtD0vtFq+yvuIvFp/6j2VL649EEOWPBf0br1ApTO+ieS1Pe+cDL7Ffby9SEqMu0sIaj1Lh8c8e4j6vQeUBb0nsW49YlP5vSG5Z77nVIw9SraFPWkZRL324UC9cDeFvaGr0zw1xvM9IPl4veqFOr2WFPQ6PZZcvcdRlzyOgc49BH5tvcgoyDwk3Ci+4Aj9PfBSCr5/iiS+dB5XvsSuXD0pj3q9/miWPOLnBz2uov67nL7wPWc/EL3EYhe+9zeivS/Asz1wME69voqku6Rta70uAzm+W6GWvc9RUL382iO9GQqNu0spsTxVmpC9","9oy+vfklXD1EeI+9EwTzPWBxQz6UhZC9ra7buYjXqjtKlHE9yqq2vdZwKr4rJjO93JOivdiEKj40f4K6O6Jyva/8+rtxihC+jD4UPHxyqr1XdpC9ojc7vnLfzz20xbE9RLz2PRrU773MwMq9i6A3Pm4bn731AQa+HO5DPoZV77xAE6y9BKP3O7/r+bxh4xM+6EOYvEt/B73WHQ89pbCzvIXW2T06I2I9k0KavS8m/7wNLkc8Lqm8vZHAtz3bwaO9rzn1PaUBwz0m/VY8BhehPGUSAb+PDOE9xUBpvPKetbybOS2+35Ymvehlqz0zS9U98QFqPcOVsb2fksI9qqgkvjUK8b3yV3k9c3/0vdMf8LzcwMO9AyeuvfUiPzzwCx+9omgfPRlwHD5RNp+88ae1vUUpE73fXKQ9wsLXPWeqQL2Q9UA9SS8ZvWWtKj5qgnc+YjyHPXDXBz6VOYK867JSvUjraL3kuUy9Y2AfPo33iDyzEWy9bbZfOkpEGL7GJZy87mfJPb+0iTyP2cO9B7soPvIxjT0pMxW+vSa0PfR1Ij0UD4U8zkaJvp/onL3cPbg8GBpqPDfZmDuBy6c74kJ2PTUDITxviHi9elGavfCugTwOK+A9O/LmO/ammj06nyM+D+RdvUzYAz5mPKM9BUhjvrD8zj3TfL89wczuvcWMED2LIQQ+fyevPdBYDL0YBIG9CTqJu4Tpx72Xng4+UEWVPQ+XUj2oPz69W0RePvDGGb3Gzlo9YYfFPfl30z3m2qQ9v9yvPYVfC77LZSY+bZ9SPrf3sbxJJ0o9zyvQPBUG5DzyS5M8vntxvv+lDz4oYdM8n/nZPb7Rmb5TcsU938O4PdllDT6DsLE8UUwFPkVPWb33gc49ZLYdvl82vL22dkA+o9CGvYq0CT7/CWY+MUvrPKsK6r2jork9do8ovegJvT1Dr1w94WZdvQNvOD7eLZo9eatLPh0PjL4tZ4S8wYYYPn954b1egr49510wvvSnVDz0Oq68AG/zPcD7rL5oYWM+","0g4sPp4iHj4Evdq9JsALPmG7+b3hKrC8bIHyPXj2LrxqASw9UWnXvR1dhb2cNz09PLXGPLN+Q7zDZ2C9899TvjqyobyKuiY+LvfZvXvQIr0pRlI9ztzDvZpJwD3hLgg+geaLvZy/0zzsZeK8og/BPZylij0ItVG+j4kkvasBbrwT3p68k6mwPVsyH7031La9NiN7vCCvsD1orTm8IkaVvDYXlL0w4K08ppW3PROO2r1jMrq99i2jPdWEGT7KTBy954O/PRhQAL1uBpo9ymd3uxMzy72jvb+9P8c+vL9Fr72skaM9vD3zPFv3lzx1TJq8vJN5veyqBz4YfIG6Qz0nPo8ojDxu58i9V5wEvqGr6DwWtIK+bAfDPadK37zCDsM9k2I4PMsFnDznEzE++h7sveyCMz0TIO893AtUPbf8kT0g7m49fWA/PutV+jw8BL692mIiPF7WLj4i+AG9PlQ2PmA7gb2Jwbg8yHMBPII33b3FYAY8HTNhvcti2rwjZrc9zPFgvEsgRDx5Syo8oBU8vp3aLL0TM4S8SIM7PaZPvD389hU9kIiuPbQFKz2uBmw9BojEPdE+mT1vN6m9k6oXPO5FOj0W4a09eD53uuLCID4dvT0+fG6IPCGiir3yl4K+m8lgvYpJMzxmIfo7q1rXPJKVTDyui8k9tmaePYtb+jwpPzK+u2rVvXBY273E99e9If+XPb1ORD1nlhc+pW7QvWCPDL4gLa09Ikoxvkks+DwvGie80Z3hPKNAE74hM3U+hyc2PdSl57z2dF++6VnuvFk0UDzMsrG87fsJvoitFD2qK8k8xVCAvhh2w73wwFE9tq2oPMH/Pr3wbg8+9wzTPJ3etrzai4+8o3MzPYLurL5GIQu9reXNPD3rALxaX9Y9frXFPczLPr7tHy0+WS9AvUePab0Uuc49dXNePQvydj00mQ2+LEJ7PX/bmj1SvIM9tZuAvikPm70ISQg9QejVvR3cBD5sBoE9fxb7va7pNL0tjQa+CM8ZvWx7h70iZiW9","RRmEvSd0lb2+Qp69RevBPbI8Pr2+K3Y8LSQgvXUWRj3UQlA99W9Ku/JV7bwfOum8NCkJvu6ADj2V2AE+QQgpvSVSpbxrkUU97fcYPgrKLL7eAEg94q9/ParFmj1LwmI859PYPZ9+Jz2D3da9f3crPM19E74ixr89m2A5veMjo72QlNG9nHjivOvw1r3GFiG+0YAdvToRDzz+xwI+DICNvQLxyL0dh1a+Hi1vvlujGD2zAF49XsoTvtH1ub0B6g8+4DMTPY6Irz2pGhG+rIc7vicrur2BC1W9OOObvOxGpL3KzIw9OVUqvmbS2b6+k5k9jRUWvQd1N74KCte++dlVvURUSz2cpKY9Nh9dPAKkaD1TIys9GbwxvYV3Gz0Jp8a+pp8lvXmo5T1kMtQ7aJIsvZeTIb5pluS9AK1RvotVlb2+7p49uZqbPfIhDztvYIk90alSviVOAr5ojHi90EaOO4b6jbs4ue09dSYSvup8kb3Zdbg9GEY5vkH9LL3zAoS9nGV4Pco3H7y4zK+8/3OFPKyU3zwHNxg+g+YNvl6yZT2Yt5i+0q9XvfG8MDzY0us9178vPZak2b2nWqS8P20ZvgRly72xzmq8MzvLvVQDNr0Qzyu+23aBvW2lkrxrBnw94wvZvSCW+T2c6oc9Jz2MvZdY8L1MCsQ9v81BPVIbxTySQiO9EYXgPdpQVz3DvBq8bkW7PdYyhTzKbk48HmHcPXLvYb2RUyw8JDMYPT7rlb2dYAe+K8AsPvZ2Zrw1H7Q7sDpCvQ63zr13c6c8rhk3vWpEzD2ayWa9nxRtPH+ftb0POqW8NmkcPZEFNb3bqoU+XNaJvcLxR74RJQK+mSfQPPM3Hb46JEe9XWTQvGPaGr3SiUi9PjFfPDCjfz3hYcE9R7IYPuPbGTyukFW8HHqIvEwYijtq5Uk9sjIHPT0yw71vR129Gs/DvKQ4wL12Pyk9mMYOvaFUK70wYVG9QVgmPkstP7c6Uoe91k1nO0/CKbz/ijy+Pu+AvYxEsjwbNcU8","hYObvO4pKTuW6mk9dvfUvUoPljxwvbM9/9rpPWOPT739ubO8lQcaPnVi6Dylzvy7peTVvCaSgL2p4TO867MKPV7Zhj0CTV89kLWbvnZgT77UVIS9bZOFvXp9vbzaVyO9gi+APHx6wTzRfjw7eRUfvRp8IjzENhS9JbbrvXUlrz2kona9QQXMPUobtzzf8tO9o5IVvtewO74mDmS8UQSqveUzDj4ZDSU7Rtz1PGZbHz4nTPS9X9eYPRxe6zsv0Qy9Y/cXvoDCsbr9S4c8r96ovBN4CT3G+RO9a8K/Pb/Jpb2OVbE9tD1LvLc1db0SB7E9rNs2O/q1B72XSso9/IK2vtDc1739/UE9fZFyvFwDjD1Y+FA9yCDNvMLqGL1Z+a09X2cFPYQI270HRj26nGWlvfH4N70Ef2w998ozPScfJT1VYaq9CrGzu95w673AChA8dZnbPZEUi73CxWK99cqOvZzUUjyY3YM9D2xcvEgP3b2Ma4M9SUoxvvrQwr3+GWI9XfFrPVEmJbyrlDu+rNMKvLRJ+bnWuqY8qv6BvYKHEz0nuLC9iyldvoOmnD2b2uE9bBFovfXvgDpnwwk9N6ZuPS8h/r1tBA685oKtvZNPyL0uzBk9r+okPYvBgL2QWgy+o/7avORk4L1ay5Y8BvHdvaRvBb5c9rC7/pXVvhwjmLuARuU9yF25vPcssL10zNs9EAEkPdjmB71d7qu6pKAKvhAO0byuf7i9M1wwPSqFlrvhlAe9a5kkPOmkFT2d+Xq9HaT7vCEOAb4ouL+95P3FvYVOMj6AL6G9uvnyPNr2hby2CB29TfA0vazJwL1mtCW99AqJvf429Tx0t+e8nwobvUAqmb2QI4Y93iCWvVh5Sj1gDAW9TCXfu3Zuc7ucMIW9oYdYvRbKoL1mnR++rhF9vfmwrL2YC5a9avs2POZa7bx38Bu+PbGYPU16E71PCQg+LXVUviuXVT3+Ouq7wSv2PBW40b27QNG9AxpTvVH2qD0hP5y8yry7vQznejyMti09","lLi/PXYAprsNcZE8GefkvZx2ub26U1s8GGEvvfAehb08WzE9ZCGNPaa6kzuUfii+JGWDPVdojL2OTni9vsY2PVL7Z73WBDm+Utjjvf5oJb716Qg+ZLWAPbJ1QDwbcsU9BrIxvrxtCr3/t7q9xGuhvQ+lir0+eby9dk7vPGTdmToRCLi9/blmO9OLeDwwzLG9cOlSvfmNhb3cpTW91lTJvdnRAT7J3kS+Me9MPexK1b1Su6a9I5VKPoyTHr0EdGI97nN9vCQ8/TzF/QY9QR0LPfG9PL0XV2A9ATqYPQI8kz1NRHc81uS3vQRp7zx+9U+9UMeIvQnCm73QuJO91hGIvfJB2T4NyiS9N61oPqvEMj5GmDs8rUQXvaWNGD8i77K4wGCPvjMyhz6THRQ7J+JOvZc8aTwz1Oe9JZyTvZcqFb0KOUg+fcJivWy77r1FX5o+hjmLvIcfUz59gg4+syPoukk6CbziuYk+joYNPvYPEr5aqqq8Rw4xPmgRfz1QYVw7fc3OPs6eNjwXLlu+wGGnvA3pgr6VtpW93mZgvvBgTTv/L5S9NrE0PqqWu70hMQ8+/SOqPTKGBz4DABa+n68ovrhiTD6yOAo95MsmPl2diTsp5ec9SpuAvQcPrD7NbOs+IgGMvRHGTD74o7i9NfhMPtB1Bb7J4Xg8vS3uPqBw/L3+r969m8mgvHw4aD3vzps9NzbyvCL6uDsIhBM++tuCPsI4q7zu4UO+arElPdAvQD4SGn4+qPs5PFLztb0iyJG9olVdPouxLT2JuJ+9Nfi7u6tYgD26czm9BYsyPgzq6T2mt2a8DnzGPe0LIT4mNBQ+bqs6PYtfFD7UOwa+ghfbPkqVWb4l+pc8ssMwPlXzVr6qhTW+L4nbPoKUJb1SXIM+TrRdvgfs0j2THcW8ArSBvELQB71Dsd49FCYbvtrjIL7fTJ08OQ7wuujD0T65c48+i/Qcvm8baT4JvtO70xFyPmnWij7Cya49HfdJPtLk8z2LgQM9JovePbq3Yb49/qG9","rPsaPXG9cr3b6Eq8+uekPsK0Or4Iib09SxdPPtzS1z2UGbU9+2afvT+TPj5GGH69xRhcPre2tL0ouis+f1AMPpRtIL1NY9G9aotcvJRxhD7Sy9w+7yI6PpH4mr1hTeu9w/YBP6pWUb6cvaK8AZ0Yvu9p8jwevKE9TuzZvfwgTb7tVmc8cvQXvtBvyr2GoB49jky1Pjs2kL1fKXw9qJdjvnIKVL3ooD89AlHUPdjUIj6DyJe99jQLu8uFa75LxM+9AK3/vPuE/LtePb490PHovsSPAb3XrdA+ANIEPg+w+L15Z0U+MxGcvcs4G77Iauc9dqsVvXdKg74S7L280JOavg4zer0yDP68y8S9PpDrKb7hQWq+TWAzvrb+/bzRcYc7MEu/PnLWBT3ZkCY+1tBVvc3KoTwMgpS8yHOjPVrLZD28yVK9ddJ/PaRuBb1p2HI+nX36vcInCj5x3OA+8rUuvZoYXj5K8+C7amHFPSy9Hr7TRF29kcrcu6lvMjwKFTs9oeGqvSG0I71Cepk9sR6dvTE8cT45LXQ9mMJ6vJoIGr5LCEm7EXKfPaI22D2fFYs8zSDWvZsKIT6iLEu+cvOjO5Gij70bdfG9zbM0PMCHIr6mnmk7SAFavlxGg73fjeM8jni4PK9JrTzIKBS+2YXQPtSLzD4Z+149Ek7MPfik3L38jWM92V4tvof4OL1/Hj2+GPFLvWNVHL1zRu892dQCPD5hwr3lJOk9GfGnO0BHob275bg9vMAjPdce47zW08+8iHtkvTyikb6WMme9396HPcigRT3qMkg+MRRnvdhOCr4U59q9FyX9OFcrxb29tle+plOYvZajhr6CrzG+MIuevZG/2j0XsgS+FBsvPZmrfj5N0I68rOOSvm7J5rxyy5m9v8gwPe0kUr60UJO9KxCdvggPxLzX6RS8qBjnvPEw772rPMM8/qiHvTRtEL5GzSq9d2xFvS5uTDwZXaa9yTRtPuzPTLw2rQW+7qCqvd7Xmb6ZyQi8NHFTveZRGT06g0M9","N7FnPZWCuDv5a128rFRYvki2trxsAxC9GIamvIn9D74lG7G9CAsCvkRjLb72cuU94tjEvSqmCb5VSd29QAO6PGWbI74oDjc+Y8tQPhateb1YuoU99hkIvrUXVb72ki6+uNfKPHZwW76B6c69Gz0cvpX73z1HXvs94xeOvaQWhTzVeSU9PXS7PUEYzr2gKC2+i+MiPcQ+Pr4ypr29AYQTPkj7cb2x4C4+qDuEvcRdFz7duje80DLnPA98c76xiDS++hHGvbdtgb2RJwE+8/ApPfocMD02HCm+4dobPj4dtb1Ant69FS6uvmHqGL3TY7A9w8uMvoiKD7xPQ4G+LSOjvK5LWz1VFUy9DCz/PI2qD77qaw6+AWaJvr8DpT1Lybs9Y5llvgQTQL6C0+i98TCivQrmwT1SezK+U0zEvcxeBb4bUsK6XfCNPdMu8Lwv8bq9XvT/PUlKWT2C1kk8PA9AvmeVBT5aKU++vuoDPr27Yz3cLiu+WLDwO6BCJb1K5/W7coi6vGbgBr4a5R++32M+vol+LD5Ss0a+aNEyvqlmL7yxom0+o2UVvlZNnj2ww6s9KkoIvcyRjL03DMq9e2xGParrCb5zmlg+VTbdvTN5L76DJ4I+XLN4vSYAf72YvMq9tfmePF0Lub05RTM9WFqCvt+56z0NCt69mHMmvYdBI70gSQ08JGhcvMbHi7yvAyK7Btm1vPrnPL5+5yS+DCDgvczFAb1VDgG9ILIEPkiDhr7WWou90dVsPTf4iz3NlGS9Y61cveNfYr5Lhr08boFmPXYP/r3/JDu8gzIzvQYkZr3MPgU9ujJVvV8ul71ngQC9O0laO4cQfL35xGQ9uJxNvcjq573ZK0i90fXyvSffDj32Hh68GYl7vdQIEj6RkFg7flefvWGqyj3Hkh69HUU3PYigYb2+mOa9G9U6vKVGert1lnw9XB1Lvt8tlj5/wJQ809MrvRvNVzySnUS9/ScCvqzqxj05V3W7Ny5YPZBWiLxf4M89VJ2gvLnP8b3/5sW9","HEYuu4DvpLyDnSy+bZsHvqdd9zxrBZ+9SkUkvTtfgb3MFYO+WYa3vDp1GT7s0oQ8feG3vdOWqL3Fim+9sY63PBwzTrtnHuW97+4Hvh1Vjr22mjY9QPyyPac1gD012ie9aXS+vbWFEbpEz7o92MYPvGYMNz1AIbG8IQoJPoZ0GzxfxiE9HwJEvfazG7tveiU8XgwoPjZm7rviLIQ9ckoePercvDv0ftE9zKIavlZTib3dJ549GUsdvND5yb0wLaE9VsUnvg/cbD2v3Ue+HFo1vQh89DxF2IK+vQuqvZrndL14yQ69g4GCvZxYFD424nu9xhnqPa8FlzzxZdw9TGHivX5qFb6A+Ic9bp8VvQGBLj3ofMm8eJUCPGZrPb1gfrW9Cfc8PCQ7gL5r7nI9tuqlPTQDID0avQ29ECwDvkekyT2aC9I9ZuOHPWD26zz2piA+pt+TPdVYJzyLumU9TnL8vXcrf7yYd5w957btPD+yDr4qm608GRS6PVmXAzzoMrm9WaPePdWXLj47sRQ93KT3vF/yQz22T8S9uwiIPQqivr3eUQG7Opo8vlHhkT36BwW+L5UwPYYFkj0PlGK+8IhhPcwwBL4l7/694O9VPsFK6T2lRdW9HSrsvYOCYr3PsLU9zmqLPSlQfb77iR69TxdOvreEqD1DHCg9dnmoPqmvzrz7t4u9zLpaPmfTXz0pAKM9G4WCvTo9ur04znm+y5oWvfS5Lb6RmE29rAWNvhSbO7yaPIM97U2PulKt8bzni4K9zAKfvXsDaT3SMSa+WGgNPZWUSD7WARc9CvrTvfx3cb4FZ3o8c5JXvb+fur3iqC2+PraNPUOgVj5kNQ+8x7NTPZgasr1jq6M8Iu69vLVLSL1QMM48mgV4vgriDj0RWSm99OqnvkYqnL1CsrC97WqxvQ+rFT75Ixi+3K+7PSsGAr5zLAw90NkLPbHWHz2XkqG9r8IHvpYNyT0Gywe+jbMKvoaXDD7qT1G+5HWXvYRIVz4tLbC8rKCEvD13Zr34FSC+","9gJKvsL9Dr2Tz4A9JG1IvWTnar35vWQ+iKJsPX/vGT2TtJa70uupu1hphb2XKt89jM8nPvi7PD7gHtc9SjQbvj0tR7wpPMm++BhXvdqVyj3Ygui9zSgaPQ0SUr6MDH896XLsvcbugjyJzyU9OGShvQeMFj0sK2a98nwNPqCplzrpE9k9G+6EvcIas7x2KOe8a/csvoEcK72P65y9DE2ZPGF6VT1W3qC9opgaPXj/w73tFeI8z3vaPcVJPTyCAq09gAT7Pc0vUj3tL0o+DsaTPA/7gbyur0i9sTo4vedDeb4Wry2+UAxFPYRUnDr48BG+UFb0vUUWtD1ypCU+aElkPaln5DtOURC9I+1ZvqcQjb3jWpO+5WUEvV42uD33oxS+7h8RPhwfm70Ojle9LeBTu55fir4hKU08FBx+vbeZ3Txt4bU9yDKcvnhDWT4A+Go9Vsm5vT+rZbu/0rW7rqPXvXzygb69BZC9mzWIu1usTb4TMh69nuIdvsJthL2PHBO+zU8XPG3mhb1xjUA+gHcyPYQph73ir2i90o4jvZAapL20YJS7dYraPapjHL2FqZ27H204Ppxra73EZvC8gRulPMxo8j1umTQ+uhAMvkyiQb4P8YU+4cU4vinBbL2rJdE9IM9KvgQ9VL7yPeC8dv6YvZBQnz2571y9qX80PbZCYT5YPYu8HFsFPTVjub1alCe+/WJdO1nOhz3npVc9WVKjvs1bS73wzMY9KIvzPY0azT1r5Qe+0HQdvdK2HL5Ll6G9aB36u+JW5z0nUVe8Gg8CPUq2R70OX329ZJe1OuHTA7ypSMq9+yzEvX90kr0zMyS+pJjrvT9MoL3vo+U9MbAivmzL0z1YHEu97AqFPrMBIj73lJy7ppZsPoJE+Lzy9Gy96l/NPfd3Sj1yO0k+/gOUvRnlJ76u4dE8pg8mvs+sPb4eMR6+czABvp4/M7407aA9wmS3PR+yzDudDjk7427ZvGEyKD7XLDu+kE9XvqhUsz356q89ZiDqvbg3Ar5vt3u+","bjV+vTgobj0oR2s8vYSSvfHAr71aGyC8s2KevaJ6Rr1x8yQ95AKkvQ00Tz0TrAm+MdPUOjQMDz4BKzw9cvpNPIgqlbwfUIS9/UulPamcJj7Mgcm7/mlBPpA6L71UEaO957GdvQWhU70B4Rq+3VeaPu/h3r1rc7e9qIwRvSdWZD3PD4y9ih6RPb/u8bzttPY7D9uiPeu2gb7RgHQ9Q1LpvFoSkLyDT6E9+f+pPPlfJD3wdNi9uuTRPVthAb6/Wzk7vBJvvdUEmr7X2i+8ONO/vTWUP75RusY9reO5vXBEAT5rbz08CXGBPSxaxb3lhdC+xNfYPQoVKbzszAC9hhIZPVQYNz6R2Nk9OXIVvMd0nT0fBJS8NdA7vWaBIruW/wo+4MgSvFBmqL2blS2+8lx+PkhT+T2/jQ0+tyGLPNPIAb4bX328syNqPUNtXr5Z64u9uY0Fvo1x47ytmvg9Z9ANvtBglj2eOm88TlSBPRlRTb0gDIq8CezEPVkJRj3M/vc82ERXvgk8ab1WoKG9mhA3vvdwrL1ndRc9IDN2PqiGwr1K5N89Sd2/PfuRmL2t8KY8hZElvSDkUD1Zb9I9eUF9PLeRbDvi4Ls7ZRaBO4xUn71d7bO9+EfZPKbmXr4LSx2+qU6fPdUkXL7wzXS8Q3v6PaEg872BGmm+dr0nPf8HeDw5/5e9wllAPXE0GzzjWiA+JfPlPMtlQr0XHZC8ixLKPbBhGT4bwC+9d0QRPckDwL1GKwg++e4kPXMZrj0T7Vy94xVZPMJVub0YJ+c9T3yKPL+O77205wK7u7NXPSPSeD0SpTE+JCcoPj/x0LzkkZO9r6fKPAjPvD0V7LW9FbUtPibzLj0cbJm9wDHeO3NMCD57Uiu9eCGPPAzvlD1a1gK+3BAQPR9R7z0ydl09YkMaPOP8Czv/iaM8tfgzvajX0TrSN249NiXDvdxZAL2PKa+8M4o1PXvQFb1TPvG8SsSLvlx4Qr0R2Lo9cjtwPZt+Hr6rFzA+tm/6PdScYr3zxAg+","3UsJPSX01ztCl4S9SFS9vMZYEL39KgC9VYo6vXOLQb2LFh69JLFKPZ4CqzxkpBW+JeqYvfelPbu065E9pUWjPSq+mrwtsWs9MNUKvoZntruXP8E9IweiPboDrjwjgmQ9WIsqPUe76L1SUWQ9XMh7vXWOFz2zxEg+jWjXPY9tpDyPoM+8zkujPRy6Sj7hJI+8R9TZPSllEz1Z7CI9XNOKPiko8L0GrIA7qnMJPqFeuL0Jtq295nxwu1uHaDtEsf48e1ZKPSuY8L13rDA9dRicvExe1zzBiYq+VZ0IvlvVOT2ZbEW9rMpmvcqsmj1tHfI8qWLGvTmBFr01op89n2qHPcA6Sz1q8Wc9VuWVOhokrL0MxVe9vixvu5DC4L3XhzM97AMtPe9KiL15s+W7wjj0POWuab4iW1m8xW7FupnpAr7Bh++94mgAPBfag70atIm9SXVevMBVXr2AS4a8fUkIPi6A/j1+JjS91mbUveT7sL3Rhse8VF8wvCOAxT0KGdW7jAdjPCoyerx2cJk9skoZPSlmz71YLvs9pBbZPVdrMT1dACc+FmInPco4B7yZ3BA+oPn+vVH4ljtvh1i8gMZXPEx/dz7tYhw9vkYKPdLx+DwV4lW9nOkgvkQYlj0oD629S8pDvGogpT2PDv68tPfYPQ3BM71XuIs92eL1PL5R7bwXGwG+9zNIvEsgDz5n4Ne87fuivd39jbxZW+e53yLcvWurQr3XVf+8s9k9PkmThb22S8G+O2qmPZECLz2Ayru4sQj4PfjyDL2lNDW9iGw0PpfOTr2uG5k7zidcvaBUCj5bJl0+DniUvIK8UD0rp0q+teI/vMp5jznNvwO9KUWZPNS+yD13nhc9t1fWPJ0kUr05RQE++iGMPXtVOz3Hm669DYP1vUoU3L3rPaw9q5rkvfalXDs1l/U9PrrevQXBCj3dyeK8WdakPSkuEr7+YZ+9yfeaPP0ecT2xQwU+eiMqPbk5MT3DY4g9GBEUPf8AAz7JgBW9SKQNvEhAjT4Bhxc9","HVmhPQqJrb1mFF69QBDMOzJDHr2PipW7shbQvXQwZD5wRAG9Bs5pvlixqTtwS8q9WIX4PZKfg70sBIc9E3OJPQJ3Kb1hOQw+tzOvPezDA74WTvq94jtyvRHK2D0okhG+0x4mPXLaDz7fFem9qpQaPCRxZTzlpAW+CxkdvuUBNzxsfUI9F4njPegPpT3r6w0+IfauPSWJiLxXbyw7rOKcvZwGlr1hvdO9AJ/pPZZ2ED4+LKI9lkp9Pej/v70zOWu9EawcvrBk672vKa49yYuCPUQwDD4/JOe9Ww/pPfdpMjxMhtw8auCOu2DSQ72/FES+4BWhvTYB0DzxIP49VU0dPloOmz1v8EY9AyvOPUCLJL6I+525LLtUPMKBmT0BQLm90tkkvQbGAL6ohUW9PhEtPbNQWD3RiaU9YIFNPaCNsDvkrMi7+EQIPo3TjD5RGm09VQUtPmYav70K9Rw9Qt4ovhNBtzxdZh+8LcYzPU31lL0DmaA+ZiuAvQsBID6RT6s8v25fPpdR+Twn6n4786KyvbT9J741owA+i/0PvmJ7jD1Hatg9vucOPvXvGL4EIJo9j52qPSzMB77Ihqm8zIIVPePJTj2ntsM5NWvzvebugr2Dwt66oSpMvvUAkz2Lcjk+RG00vU5VRz1zmvo80VDpOjtiH77ojv667XxMvhj9bb65FTG7QYuTvdF5OD0p0qO9MEyJvWJOh72buyI+KbCxvRkwir3Y3Hw9iisZvYPSdb1ILL+9Z0GdvfPrhjx9tiw90u+SO523KT3x0Dm+ljgTPB9VG776R1M+VikFO5wyXrxhJUY+fvixvcKQXT1+L0m+m4ADPd1wWL1kM6Q94TNTPYUvmLuP3PM93OCmvcrmTDvKlpg9BcMtvjwlkD2lace7FrGVPDMfJr7YGpy9PEMvu5kuv70kM7i7+9YxvbPHXD00bgw+vGxIPSAAzjtsqmE7bgkFPoy92z3oLCU94HKhunsu+z0txA65ZkMsvQ7HIT6c5BA9SIn5vdf7gD226NY9","2K9Gvc3Y5bpO1gs9/gIOvV1Xsz00y7E92IcaPcBLZr1aUFE98secPSnFd70Xo3E9CQt+vEow+r108Eq93w1evHbPAD1l3Fm+hvzMPAims71SXpm9UOKYPfc77T0Jo3U97tOWPYtim7lY0dM926oHPvVmIDyy7Y09Z5pSO02q2z3bdgw91poEPCb14Dx3nLG7hCBOvuXBDz2kKNE9EP+oPNTS0b0Rxa49hXUVvZUiMD2QpSI+0bg6PTpTzrzktDu9KlUnPNciO72cENY9FXFNvUO6pL1OjoG7quM5Pdd+Aj7Lk7+97rp0PVqsBL6WBAI+L+4xPAtXjD2OzVU96NwsPkM4571qTl+9CLyfvdVAsz0dJdE9GpldvW1mFT1r5848nl7SvTvybj7XSfG91otIOc+GUb7tBa693DVgPE9vDL40nYw9Au0KvXH0d747IDs9I8DQPE/aWL1d/Do9rSX/vGPgyz3GFko9g4I1vMiR9LzMdV89c+mFvaw9/7008Qm+ZKR1vZFGAb3+e429ptiyvcZfBL58Y0I9vYJJPZ/f3b3BvKi6Y1IaviktsbyXhFY90eXxvf18Dz5WCLs9mlBCPR9C4r13K5Q7yXMpPaY4CL6jJ969WZtNviVOP70xpHc9crBYO5WDSr0WWpw9jQpcPYMnC76pAU27lLYWvjMgdD06SFC90kSCvQMW/bwoAx09tI+HvLwq4z0gxJQ9Y837ve+Efb0fUJ69Y+bQPTSLLb0JvwG+ces5vX5XAzxuI5q9wcgOPNLMQTyez7y9m/qTPRscnz1vC6S95+U0vhjmwz0YIKS9/LplvSVk9b1NOt49c0PWvQt5GL1IbI49MZ4UPG3DdL2rNQI+nofKPHD4CD5uQoi9WuwQvsHECz0+ejG9fsTpPXeJwj1GC0E9DKKVvd6A7byuMt69RIkuPkghTz3TqkW9OE6fPSiJMTxOPNY9KHwvvh5D9L02j549+OjpPYf1ub2gq/G96u4QvVM6WLxb6bu92wgLPWMdZb01tfS9","g6CivUHK4b3OvJW9uPYkPnQssD3wkuE8UASwvaMxPD3BQCu9H7KKPPqZTb40EKu8T2hrvlFB2LyaHx898Y1jvqQD1DzbmGQ9lVS9PXIP/r2/o727zvcovd0t0j36LwG+TP0fPsnahD0xX+09OBbKPK+iBT1l8oq8zXkvPQEa0b3RgnI9xQ8cvQVJR71+/PG9UY+vvArDXr1acJE9eLsGPkQR/r33SMg9NnSqvWYTIrsG5iE+YDOnvSA/Iz3c61A8ppRfvRNZYb0OCUc9a1uSPWYxzzxyygG+U7Z3PRMnkz2yCSK9z7cfPVZbRL3uz1C9Y+8FPQPIAL0Czzw9kpn7vT9HKb458rY91EDLuWJ4sj0Mwmm9w8vivfvPyr2mHao94gokvRFUVjyZk3Q9vHcAvoxKyDuvSio+p02oPKoUTjsvYxI62LGFvYremTx5yBA9Cw0QvZ1KuLvSLE89iwwUvCvm7L0ccDk8UUP6vcUltD38D/q8qGAfPjeoYz1G2Js8cARoPLhkDb1vZ749g69TvTXZXj0vhRM9Ry+8vRBpar2lCwu+q3K5vTPqvL06VRU+n3YWvsqV4zxmFLG9Ex7sO7eJCb78Pve7AY9GPXbL3r1RbCS9K+kEvVFe0L26iYM+xwsUvfR7Lj32zXK9IKPoPYzry71kEz48WVCIvUHRkDyin7Q9hQnTPPwV3Dx4jEI8qSeYPMqHirxuyDm+bW83vtgSgL6q9wG9ukj/PXT3hj3ELdo8gifmPWhvzLqllHk9Gfilvd1veT0L5iO+xvkMvasBkb2WAWE8ZE28vbVoXL4VgIQ8YH2NvYYQ1b3MSpS99Y75Oo8567spCgq964YuPcnKKL5P97694ZGTvblJOj3F59K99Yx4vQ9xbjxiVBO9SphtPZLCnb7TIm09j0YoPix65b3WrEG7BfYiPc5fmj3mck2+17MBvq0h47zECTq82PY/vQoNe72c++w9ALmcveHhO77iX+09IgMXvY0khDxWX7Y8SkSjvZGQOb6LpjW9","5n+IvQgKzT2ZN788GbXfvZ5rlryoVFG9IwnlvULyib1R0Mi9vGLBvNTVvL2BtcO97DwiPFO+sT2B6Mc9InOSvXeGFL1kovy9LHBAPiTDmb1rHo+9FbXmu4cQjL2HEZQ8JfRNPV3+LD1mK9s8F7hyvWkEnTxFx5W9TV5FPVx8PT3dQFO9pFkmPDLsgL7q7nq9JTc3vv8kJr3/6yo9ns7pvVJmyb39Ljq9Sk5xPYKsuL2wEZy9VwyMPL3Iib0zzR89JsZiPfs60r0M+yy92ASTvTdnsDua6wE9BOuevauTZz2XJsK9eA1yvcj9yb5Xdau9noojPt/Xcjz/sKW+jUfdveclJr3mY069IDilPT1JXr2vXgY+RWmRPUGebb2IIc69b0SEvY0u1b0S7189VfiCPMk9EL6W+3O+okIXPW88NL2tX7c9ng8EvdTZnL0YVoo9/3nfvb7axrwYuGy+QYThPDp6Wb2NZ8u8b5cEPVRCRT6C1Mu7JTiVvCPRJzoZvAa5smEBvZVr3D3AOgC9QwWLvbYAJ76AK0I9sV9xvfhZqryKStO+BCMiPZ7JizyYHji94aaHPYPWd73VMO29vNQdvei0E70pNVa+Bc/BvMYhHLzfte25i58avVu6mz0Z/Fc9FvedPRbIuLs4+RS9IsHlPW0KQL0mZyq9EJ4wvKCtpb2Misq9xgyMvZdS/D1hgXI9CzB9PXyMbj1TKBw9pBsFvfhaZz2btwe91qwxvfkhFL2h2pG6w8cHPjpkhL2bXrs9TGEEvugDkr5K7yy9zAoJPCYcZrytOFw9P5xOPSw0BD2l3FU9MBKMvSydFb5FC0694Ge3vRI2dr1DQn09hp3UPHcog75Jyw883JMKPQu4dT0OMja+DlZmvD0NwDxJTEk9lHPcPV+aVjxflaQ80JPOvKwz+r2hV/u822zevaux4L3lK+G9V4xdvbWLwr15h4c9PVUxvnWMFj1LN4M7XzRFvox2Gj3HYBi+ZwmkvHgrvb2UAuq8dIXfuvO9Q75WdQG+","gp6GPfX+DT1JHmY9kgyVPR2n8r3LPh8963akvTTwkjy8ukE8igIKvphtHD6p5LM9gkDuvZK+RT1qmZo7g0VOPZwrvTwIJ0O+1dWiPdoLHj5ZGYw9YGNqPR47CL447IC9VfDnvT8Lirw2u4Q9OQemvdI0j7ycBtW9yFilvZ41rbtALEY+3SPUvfdOlj07/Kc9+A/bPckRFj1iPY+8tjI1vcd2m72DHni91mgMvRc2xzzh49m8ioUGvqBp1b2Ockw95rsuPrJj+r265I88wDufPMvnQ7zjn0o+2Y7svbtErL3e7XS89FjAvW3XWTvFdpw9sWlAPlukCb6W4Y299MpCPln1Ib0KHyQ9XgQPvNW3bbzQ//i8iYDRPTcylbxdHiG+lDxQvXircDwRjLm8nR18vdiWqbvDp689n9KjvWjfVz0DkP89+zNDPLVgCr5JiWa8uaPgPVL3Hz0knrw93c5avVTwMb1fIbY7X5mxvROQ67za2KE9p0kvPXmcEz3Ujos9bA0Kvhpv+70ZzCM8+XAsPinVdLyv47e9dc/yPZIBQL7+KVA9Q2BLvgZDgb3pP/s9vU0iPQE7Fb5zuu091c/XveVdJzur8hE98xY6vDC1DD7wUoy8xRMDPmI4Xbypmik9uQgYPk+fQD7hZMq9py80vdUN/LxcgT6+mCDTvaGgBz4wsXG+pRp5vWWWhrzrp0s8GSwWPBBiUb1Fyyg8ZCq8vTwyTz0t+w07hxqYvBkCmb3l6yq97C8FvYEZe72VnV677nO1vU4XFT7nm+09iA1IvZJO6r0rHsA9v9qVveQu/z3wSY09JH5MPsFrM738nyC9EmaevWJzybuMWZO9TvJOPRerFb1wzJ+9ctW3PaRjfb3KHcq8fNBmvbIuZDy1sPw8MFUtPXZprz2xHpW9yoKzPf+Cjbzoc8882WrMvd2Obb1yCp48oUf0PAV1uLycCbQ722jwPBF98j1LmWa8mpRxvTXFrD0gNZ69bVqMPCgmxD1SO0C995aNPFjLZzuQFFE9","smXjO0PYKb2pvt67JdkSve+mcb6GZRG9n64HvfBT/T1h7x6+f12/vTXVgz35et68WxKEvSfsD73c57u9SUWOPHRADr3R4wS+OZJvvdk/vr22H6e8jNMcvT7yZjyl60y9fSi0PCO2ozwsa9+8ugPtPSHtUz0A9x07cY2COWheiT0fRkS9NQ+6PG0297u5jAK+AE1evLmcSL249ji8M6K4PBbyMjx520Y9GTlkPWknhr3zLdg9lPGTvSbCtT28iIW9hr07vS57Cr7aWn09+bOlvKbMj73NwH08lnNMveTggrwedBs9kPjxO4ntoz28Tq29Cth+vfXKojxQku09GksRPji5T73DQUq9Up+vvIllqj1rqvO8nvKIve3jBj7x3ti8PoRwPtUCj7704L89xxWiPP3N+D2wUq+8F9ZXPeu4lL33yRY9hTWDPD4K+jyuGEw+yotePQOkBz3+0dO9tQoUPcM58T1+LfE8D0UQPo9FpT1c0y26fkD4PN34U7yXKpk9K4USPLsz8j3f3pA9ycSJPfTNiz0q9NI9opi/PQhylL342YU8YqeIvRkRyT2qk626AM1wvMq9s7w2oJm9ybF5Pdmi9TtadOo8UJANPmJoJbuMU1o+/wauPRyqtz0Imc09+PnTPdsC/jyoBpC9FxIYPgBkH75jBWq9fIxhPtZgdb2FGnI9Lg4KvV6+xD3Wk8g95RbkvIQ8qz3eePM8CGohPb4qADqq9LA9664tO0ggrL1kUZe9o3qwve1SCL6ExW88Wq3ePWt+Bb3nwzM+PfZzu+bncz1brSQ8Q9TGvUz+prxlsw29AvhLPj5VuD36K4g8B3+OPdTSMz7N25g8sPOsPI34Gz65fvi7rZ4TPl7eGj4h0Nu8UekzPZOhBLxI5uS8XTnSPeQ7Aj720LQ94hVwPTOEmD1EX8C91iRoPU2/7r0eI4U9MO+tPSlCTj3RKNU77v6VvfCm8Tv8gQu7RDKxPSJhLr0OvSe9Ncj8vE4OVD2asCI+Zb9vPJQgiz6VkpI9","LzAqPYTRRL1Z7oM9FUmFvSs7l71RyTw9oKnjPaDc3T3M8169dEGEPZjoIbyen4A9anDKPIUXSD0O1t08kWxNPJMTVD2fhbm96UMQvGIqkDyu4K68yGzZvQHturqnsYA8hPr7vRKcqT0lesY9gbCFPXmU9byDnLA8iBkGvr3H7r2AR+A94QjPvRDTVL2I9bC9YJ28vJMuUL1rfJ495XXBvKZtBz4qp4k8uezYPcgf1b29+HM9VpasvbRB/j23Mao8UeUQujoF9T2IaQ491eAyvXsqND096iG85/TIPWbVfbzktqu95Y6KPVMCgr22c449AqgovY3iq7rykYm9EHmavemKybsYwPy85x8cPvdqL732Ihc+cekxvo1M5Lyi5Io9FoZzPKEAiD2NFME9vi6rPVdN5L0cl3W90/kMPsz1uj0pi1Y905N4PTV5Pj0YgAk+DP09O4RKsj13CLa87YXEPVVHvr3HF1K8HxTfPVgXkzwzN6G8+TavvSeNm7yUs5y9PbG1PSwmGTwKrHU6YyievbvB0z0fq/G8j39WvRK4Qb2XfTE8xcAJPv0DQzuJFD48s/fiPeUrO74vmX29FoFPPHLeKb0yMbG8RmTqvWjuj73eyB476ktDPcP4Gr7VVLa8LYEgPnEKBj4A2V09crKGPbc3DT76Roo8uHP6PPZ68LtOFD2+DJVNvY0ynz1fU+S8KYkvPRcwfDwOm7s9zFjNPdmUnr0qMAa+oC7sO2L12rwqIQY+jumIvbW1Iz3nEYY99AuHvaHqj7sLzZA9cKsCPkkIvb3VkRO+zKbkvRQyrj0SiQY+KDq+Pfzfnj0LzAg96M4lveYJXL2ji7e8Jq+3vVR1R74m87e92KcQvfky1DwVOZ89fS6WPRbMEry9Hiq7Q0QHPWlBRr0PZW27GeX9veq1Pz1jLLI9yHa1uxRcFT0HChm+Qnc0PR5O8b2d6BE+cDamPemoir5okS88czMZvViSwb0C8oQ8Gf+ivW3BNT1iz525wU4gvRo9YT1XYJG+","rGFOuzIkUD20j6g9hBmdPdDomL10qaa9UiWkvcBMUL0xQ7S8PZUmvh03AT3aPJI8RbxNvjIz5b1E7DM94Q2NPaHYfr2fmPM6Q8vKupFQQL2ZeR2+doU/PcNAtT3ifu49UCUGvlrXFj6smhC+SI9MPq0ssb3wKPo96uHcPfIUwr2bdoC92SA2vS/FTD7Rh3C9oYjQvVtGH77gGwM+A7Q2PhWPo7vJ1wM+Hga3vQF9uz2UswM+kI68vA/qVb0DJnS9h/7XvOS2DT6O96O6fI/2veUSkT3qEcA8yGsvPZeF4z2G9gi+gs1Yu5KE9byMLpy7Af0MPhRssr3Rsnm9Tdc9PXyj373Xcms9cMVPO8LlLL3FUaA90NIdPU7UBD1yDGm+CxG2Om93+T3P2ok8rAAWPQtXE72Mi8Q9iaQlvqJhOj2VUKm8wnj9PMEbLT4+Tkm9U6bNvRY2db2pqx29XctfPt3T5L2bkuk9ksgbvVNsa70UrMc8lteLvdzRzL0YwMw9vI/aPN+/k7xhroO9DeFiPLhf5rvb6Vu8hiWWve+IP71OYsa928jOvAJvKL2eP0G8WkLyvIrKR75H8KY9LC+APYnCGzw1Ja29R2fEvTuP4z2xZou86dsQvregxr09ZgO+Gb5hPSaljb1lR5q8xlQJPQq70r2ss929AIwcPU3Vxr14QT++/x5dPGS40zxVcQO7EU8vvaRMK74i/mK8S8NvPXYuP72bv8u8CDPxvAsh0b3awji+SYWAvQvFh70oGEm9bad3O/PKXz3Loka9YFdIPhhz5zw0+Cg6cFlsvttasb21YCM9JkZeunrMdT03Je26qmSBO1tykr7gkg49gbqfvTJjhrpIho89K3ZVPPox1L29kgg9eFOfvMcEE75ZRNO9iwzQPMqbu7smEKm9vkvpu0HFUj0SiuS9f9cAvvakobykUMI7LDVwPROtzL35TEy+2TJEPWmP2Dz41VS9FuzmOuQqrL0aUJa96CBKvS3yw70TKQO99WQ/PAPl3LuMYAS8","f7fGvJtVC72Wxue5R9qNvS+6p7u0WTe9Q0XsPeGV7jzOVI89UdJcvi2yiD1mw9C90ARmvSbwqTwE/nm8BMmtPWZIHD0/o7k9GESFvfypN77tqow9MC6qvEPLFL212Q+9r6mcvcqprL1Y8bk9nIgdvmLoKrypZJK9IN+UPMcsD73Ll7+9GP4Fu8LHpzxoo4K9RClnPmrAWD2jMq08zwuKvVwihL1PX3Y+PU60PEWu4r0Z8no9HXd4vI1b4zum+k48ByWCvOe2BzvaUSa8U4eKPe+j/L3iWw6+UXLsOkJnUz1XQLg946aNvfjFYrwg9GI+ReAzPQa/zj3Kdk++aE0BPcDugz0a8Vo9RWeEvcrJUD0YIgi9ugdhPe94cT0FlQ6+8GMpPLr3V70uFoe9AYJmvq+wlr1buWy81wQVvbAWXj0CqOE8moEDvkkF0r1LfZy9J/oBPXMum721C5M+COEYvuz9rD0Z/t09VpwQvNPhjr1cEhK7SWFJPOF0nz3nKA2+qVLOvW3oKDzc9/66FGvDu554Cj5nvNa99H0WPdXKV72vPfw8a8HKPQDY8T1vDC08uofZPXQ4m7y5BUs9qb6QvLju7bwBIP67BDM6PfZZqjxcy2i805uLvfvurLuG+A09CnYZPqH8WzwlzDk9FZwJvoznN75p14Q9vI0NPmJZk73LZKA9LzT+PH2T+zs6kj+8gwKmvZNydL6BF6E9DmL/PDDNfjzVPZ+9o7iOvRuD+LxEP0w+M2KZvZiZfT26R429iMvPPCbcDT5TPV28X6BFPXhUbz2VNhu72V+ZPc4GWryaFYE8p+UlO/08Irv0AMa9KJNku1YNkD3TtxQ96wGWved3jL1Syus8YONYvu9oD738/iW+kMvZvdWGqb2F8oM8Azt9PfKGq7usQ6W82BkEvjS48ry87YC8qV5rvgJbgb3NBL88q83OPUrTeLta+ZE9CFchPuryFL7mYJO94WUBvHkdxbzWAkw9B6smPXPAyz2dH9e8qt7evLByiz3ZFpM9","C0i3vUdeiz3N9jK+se3MvL6Yyz0P/ZM9TfIevcgzUz1y0hK+Syo8vHgMRL3UAcc8VzE6vn4ioLzkwKA8beIWPW1U9LxxshG+8DjPvTzcjLm7pFw9SHwvvP00mr22AGC9Ds6xvM4+Nz3445O9wNWgvTQuRD1TbCU+I0BHvSzhhT3C3hG99wPDvENTRj0Cb/Q9bNmCPeR5U72eHNi8etEgPZMgQD5bQY+893SGvSk2hz0ChMg964dmvqh5lDz9l7u9BGIJvlajf7z3wZg7NToLvhTG4rwv9mu9HFrxPOXLlzwD23k9S6OAvSd2njjT8TS9uibdvUBsvbvz0j88zA6EPInMVr0qflu93xh3PfouMj3nF4a8pBrsvW8BfT7BOB08naGvvIo4jr7hwl27eOJhPYxkyL15hFM9lU66PVKUpb1pWS29OGO6PT2D+L5dRNA+sRbcvPaUPD1vMN69I0XlvaFInDsDWNw9LoE5vp6zTL3+6FK9KATTvWJ4gL6RY7S9bCS+PXj8nb36E8299z8YPj/TrrysAeM9VA8mvgKTrz1O4DS+ZdafvCn3hrtPk629rDzCvdMZwrvrztE87B6vvJv8i7tEYxU7p5mUvZZf5D2xk8Y+Dhynvd/Twr3R3A89ZyijPTzY2TyZkUG+DrnVvijOnL3NGnw7EDMOPvzYT7x6R5685eN3PDUyV728N0S9Gbn9O6VuGj1XEwO+jMrQPVagPTsBQok8JU9dPQ29gL04OQi+pvh3vDx7mz2944a9e40SPTYH7T3c00U+DdYKvoMNFT4r8os97PGUvPaiDD2hLQg9rOktvSsmp73H5Qe+mshcPWBx8b02wCc8RiIuPlymcD1EMqk7RbnZPZSfhD7NYQK+6GM9vWYUvTs9Om4+3fdiPos/zb21Y129DIK9vOwr2Dy2QtC9sAGAPQFkZ74lY+w8546fPcEnQr5PXbs9wMQmvridPL11zzg+o6HqO7/FDrwiisG+y76hvdV6m73YDKk9IASDvVYOqb6TrQW7","XziSvIoGrD25DNK9yWafPMufYT1+JMa9kbrtPMp2j74Swua8JpY7PpaK4z0nhoG9ZHn4PcZvkLylUxC+AbdXvbHUKT06HCU+sQwFvv6MqD29w+e7xIUaPmKRSLwpGFO+jo9YvaeqnT1peOG96xEJvhMY372vKAO+9nTJPenWeDx4uLU8zcVKvawYVTpTrO+9CFxoPTkPV7un6kg9qjajvaFJrL5U4d08lLUsvpNporydV+G9UED0u30MI77Z+MU9sl/cvWANTL4MR769yOcRPO2Bwr0CUgE83zagPCUDTL7q99W8f89HvhSlK7y0NRc9vrwjPQP4try5b1I9GKIWvT6+Cb454iQ9rqY+PVuYVj4Xoja+ljsHvX0vF74v7B++Wg4TPrMEfjwN/Ya9Ro0mvqtFqj0gGqO9vvArvUR+cb0X3bC9ojQbPurM8D2kb4G+TbwNPrLyB75fzs287qaFPh/X7TwfI3c9YJSfvfkHiz0Mlng9y7nvPZi7c7xdQsO9aIoSvrMNJD1nwDa9w3YEvtu0F73jP/s8ZviVvegDEr2Z9yE8XpspPjU/8D32eiG+bqbrPTNrNL35ZCU9VGQdvmhhhL3hF+c5E0htOyyDGzxLKOI9UhJIvm/Stj2eOfo9FXIIOp+EaL1ZIa07VAGkPTLflL2/mCo+ppIcPc7MqL0PRRE96788vPwqBjz0wRc+E74KvlEGmb2mNAk9VegVvZh5Rz7Hv7Y9xvbBvIxYCr20h4K9sbHcvfHxhD3LTkq9VuYCvoNvkr4+ymq+UgpXvsLvhb3tLSM+kBQBvNDzVr3FoCU9cIsOvSWZlTze5ku+iSf7vOykvL0VxAG+Ro1ePclvnL0HAYg9xwSoveU0hT3aAdO95ScLviwIEj1Vxua89QcevS8TTz1A+RA+fWIsPqyaCL2afii9dDKOvRqaFj3Sfbg9uhAMvXwBgr0gnjQ9SF7KvjSzh7wAhja9Q+xbvUo5oD2zaNu9EjkWvRMh5j23l/S98HKWvcLpED2aTT8+","AewCO38y1jwhiPA9zcycvSkvlrmqtiO82gV8vaQICL7u+vO9Zcowvm8dv7uMfoG8+q/0vZAy3r2YGM89Nn06vWpicD3l9m69nIfHvUDSRL0bPZI9omPCvE+XGD3V4yW9TjkSvn1eP70PTQk9NHquvdAEJT46Olq80P8OPSDkCbwwaz2+IMayPJygBD7bqlk8BHqqvPvjR7yo2vs8qBJ1vaStFT6K94+81wj4PHTyirwBJ/m92fy3vMhkIL7AdU89y6wwPT4gTz1rMV2+YJLQPXM6pbsHFq69VS3bvUCXOzx7T6i9f/XrPAWRLLz/YkW+YudevjHNljvENEY9dcinPOqQJ72ZohM+/OaevXIB5rx6rTS+KyCrPSjIAD0xT7q94SOrvYueD74FcVm94vaKvFdz57vHgoC+GUGePX31xb0Tt7C92FrJvT49CTvWUBy9DHslu+EPI76MWxO9FRhYvcvrmzwgCX29XnlRvtt3+Lyyfr48w9GjPWBcn70RHSU9waZcvMGf4j3qpjS+l4AWvPAtL70KGjc82AmavJHndL271Qm7S0brPY5Xuz2IlOA97lRyPd5SXb0EJBq8u7QHPQEpKb0aB1i+qc/9vBwHYTwO9B++ysETvdT4SL6ZR6g9fmodPuQgcr6uJ529ZW5QvXGLDj4ZXmO9a4x6vXIVsDxycYk9krLhvcBaOL6Suqa9zxj/PfggAL2jfti6EILyvEWxSb3R2xe9HMBbPGp6bD1vxbK90KEZPMIy970aoo294wvPPfHFZr6W1Ba9UKEbPDs10LwFv4g8PZu2vSzlybzWKSE+6yGzvVVNjb3Gl7q9W9/ZvfSQOL3FAe68naCTO8jVUr6ecJG90unBu2yQXb1FN4Y9aNYbvXhixL3/wfu95Eqyveo+Iz1jdYQ9vcOKvpqKCL3v5AU+3vSfvD37H73x2TQ+gaaPvWLNr70LQxC+NVkuPZFnNz0YdGe+bwlFvsJNFL0hqVG9X2XlvCxwAD7rQC06Hw04vYnLDj7clgi7","TWSDPB8yOD4Xp1+8YL/sO9d2jb48xa09bKsUPcEdWr3yc4Y8gxOkPdS9Fz5hDze9F0LtPFTAlb1ev0k9LsQCvmDHwLwTQQc+YxmJva29lb4IZxm9/U+9vA+a5T3YUpI9bzXZvQD0/j2W8yC+dcnyO7AiBL43haG80DALvU/Kg7x807M9kWrGvV/YWT2hNTc9TQiqPXVUk7wNgBU9lmgDvoWijT3FqdW85N7IvaUCqzzS6CO98wZXvDbz+b2M5/u7I9kAPkQmIrzRb6i8z4T4vYxoOT4SC7o7UbMovdHMCz0X4iI8XTXXvUymWD5cGre9bSaNPUUNvjwfJpq9XucwvnRVRL7arvY9d9jEva/7F74+8zk9pD6jvbhu7zsdmJa9QPWwveQE/L1+xh4+9qzlPXihtLxznq29XxwEPlw2Pj3NYda9kNDOPTVwBzyFivY8qvZvvYK4wb2jS6E9LdjjvdoSqD3+NSA9QeCIva9Xpb3A7G87VegIvnwouzzH0J29mNIovTaUFD6YYMi9x1zlvcwIqT524CQ9TSd7vK8Tpb1AXUi+Jig7vu1ysD586dS8WPtGPkgeST4qrZe9mfhGvR7k673OkhI98UQMvk10hz1i2hi9J22pvrQfjz2j48U9U2zpvRU/tz1gwxe+HpioPX2YGj3ZiLI9OvdCPe6Kxz0Ec+M9J9KYPpBYg73sP9s97xq9vV75tr0pWiG9YyN+PraQgL7KCYq9GhIHPWiQ5j3t1mK9vDQIvcbOtD3HqDW+NGr0PZ2m4728YOg84heXu8fTe768maw7vfEBu9KDBD6tGr+9pG66vYiHJTt0M+m+gY8VPgZWFz4vbpG8LKoAPRMs970k9iY+J8qgvdi54z3ThEU9RSpqvkDtyT2qkfY73e0VPt2cDb0TUYa8KQmYPR7v0j1dY9Y9NPtLPTyxkDzMEjG9YrsnvmCcxz3jnMi9/0hJvgP/czyRjKk9vNSBvd6Vmrw4CjO9sEPyPMbXsL3ckNs9/3iDvdv/37wmsUm+","HA1DPSPUgj0uxh29db82Pb1iEL6pvZ89i2m0vSIXxj0kv509Ij1uuy7p+b0D6N29tU5ivZ7Vqr1eHQc+00AkPUGQCT6Vroi984E1u3EPfL6yfTK+WCK0PerEpDtmN9I9dIV5PYDkLj3b+pg8p5QpvoR+3j2gepQ98FdJPYwTfbyWxIG95rL7PAvAnD1NpJ+9tsYOPrGjkbybeFi+J/6UvSmbML0zrR0+o3qSPbqejL0qafk9HXNkPjEv8LwPl7888tYAPtZcd70TUR0+DnmevfB2dbySI3I9Ewt1voz8Yr44jKY9SjXJPDzoCL0Gg+O9HR0SPXcMBr4Ua6I9NR1Qu6gbqDxn5uO7WQ6iPXPyFL4py009JwA/Pd73I7yi9pW7iNmDvM9DQ70uKWW9j9mfPdXsCD4ZST28YipCvPRHfj3UigY92Pl0PBYsF71zc4M+m0zZuo5Dqb154vO8Z+oMPFiQvT3arYg6FhsZPGOYabu9RpS+ujw+PZw83rxl+PK9E4fiPPRkCr0RYxi+6nuavPGeuL6vlgu8tN0zvg747T0GnzC9843CvZflKD3YUNq9yjutPbv9jr6B/Wg+VLLTvfnAhz30Fww+gDrKPFf5jbxYqx0+hBfkPXj20j0mUQa+sKdqPlbnTT33CTm++nPlvatoIL0VZZe9ad+kvWS13Tt9pCW+bFJPvsRq3TyKPQO8ouwputSQhTyAghm+8FIkPZUM+jze6Mg97WxqPRjGDT0raFq9IIBWPliXNr5gP00+6hNHOUSVib3O95+9sSAOvhzkhD10aCi9vcMKPqdxCjwXN9E9Vw2DvfXE5bxJedy9sX6fPSY4hj0NOBI9cdK1u5oEgr2r2pO8QEKBPvuL1jwrapY9eHdyvVZ+UD5yDh29oBhJPF6YF72r/Sm9J/vzvM7oTb0O1wE97abfPbaTUT2EubU9qMCjvfhj97yjr8+7WoeyPE1bbz2UegO+kmPxvbYyP742mf09z+szvnB6HT1yCvk8v6bjPbZyG74nTqk8","NYQTvAdAiL6tmcM9+XdJvZS5uDy3K8u9Uh1zvB2h/rt4MKC9QpQMPhC5Xz1l2iq+aZiGvfhImTyU8tI9LVZTvZhGbT5HAz68VxWvPVV5sr39you9/5/svVYMVT1T4tM9aYuzPaYNPz7jHt88ymZlvcZwRD06/Ka96PvGPKecu7w4qIS9SRNkvQMUID0+YDO+apkCPQnh6r1b1oa9SHPTveFRwrrBAEI8mPkru2r0UD6VQrK9rbrxu86yqT2sH6K97NG0vQAoXz7WQmS99n8pvqyrQT3vMVA8UPytvdwxLj2ZEVs9wKKCvTENkj3sDfA9G2XovG6y5j2FhVm9AIZmvlPowD0SMq+8hSkSPXZyO73QbTc9xqZdvg6ppL07heC8unqCvRagTTwQ7BQ+m+kIvg03q72K9YG9H7PTvbfju71q1XC9nRmEPUPmv7zwtY08n+Z1PL4ZkL2/jZg9tObsPfku7DoCTMI9JU6UO1mLcz0+Eik+fFLzPctco7zsdZi94kG4vQ82lb2hnru8Q2znPaQyLb6Resi7P8havZVIGb1++Yu7yL2MvRWY2r3aBQy+KoeFvF5llDsWnrw9kL9BvQgxqTyAZhQ9fqcEvpzLjTzqmGe+toQKvnb3QD3aNwI+30NEPiYX9zyNGie8z0GsOC9OUL3Ch8I9GE64PcrftL7FbLw9Bf8SvkVdR730v6M8CCeJPQG3GL2hLak9QWnuPFR4EL6l7b89sgu5PORqjj3p24a91GIIPLlbYT3rBXk8HbdmvQ8Vdz22PUu9Oogkvo6S4jzzbxk9IXl9PPTULL0xtrq88rIQPlDWCz3sx4c9KyVrvSg4fryZSfq9pkIePff0Rr1tzYu9wXbSPTJmOL3AZPm9jWnPvEWADDxY0ds4XHMVPTunsL1RijI8zXQsvVOUaD3LKMk9G9OjvQRuxz0crd490OPXvI6C3709+aE9FATQPcFEgLzDV609EzvtvKDISz3QvCw+LBZYPWH8hrstRdc8sdLbPQhtAL63Ky6+","uUgNPAmWyL1kBJC9FDdQvXJyx70u5vS8MnsGPkGvUD3viBQ9+qlzPKF1oD3T0c+9sHP7vDddr7wsdBY8F2khuCSeXT3Ndls9KVIgvpjtgD3j+Mo8N6G+vPS8Kb0BJGs9aDgEPrpsFL4nQkO9qoHxvJQ+1D0Q4pe8sDHYvKtd171v6Os9zi2gvaUSb72w0um9u0pbPEycgj1HNC+92EnVvWsHPr3hwDa+OE89vfaP4Txxnte9PdC3PcpS8j0Xfv688ihWPQ5Tar3+m369fRySO8A17DvyIPi9dVn8vHJHZz3kZbC9F6cFPgC1Qj7U1vK8ddRXPGDRDrvw/ZA+V+2RPepjUTw4jpQ8iCmaPccnqL3QgB29Y7ydPfv3fD2CIdS6JLcCPe7mDD4MM9w89mULvgy+lr30wzc9dsYRvnxErL1qqFm9fhz3vWxH/L0sNWY9yeqruwgcBj2M/i68YKTsPTfQ0z38hys92Te8PVnn8j2LWHs90VuqO7bSJL05bys8OqYWvcLdaT2Tvhg+a9EUPWIG2bsdiRa8kgkdPdGWCT2Fp3Q+vrctPdfUITz9toy9XE0HPaaGCztd4o29LhaTvV27PD3gCQu8nDbcPDYxiz3BKRw+c4h+PS92+bwggTq9YStdvWcCpD1weBu9KfgQPbVJob3aa6I8//NAva2BDz7JPpS9TNcKu7yQY7zltpC8Pp1VvYpPFb71iiA9yCSmveUXybzkTyc9zQIAPXJAEb4lrTa9+uglvuYxZ72T7BY9EAcGvpAaYL3jCFG9dl2UvfvXKj03Pak9oMhkPZTTZb564zs9jGxKPWNUCL4Oao090z6vPEZxor2cvv+92tPvO8+qyT33UtO8Z0s2PWxGgLw3cIY8Bh40PPq1Gj0LvKk9UpRKPbgSCL5aOTk9ibSHvuebtr3mW449Zqt0vSj5FT1Q9b+7UlQUPgKForysTKm9x/skvlebE72MGES8jBhaPDVpDb2iOeO9hBQOvXKxsbsDRuO8bmCyvYyDnT1kcHi9","S9JWPcnnEj2aMAq+altoPZ5JyrteFca7OCiZPNm6UT2/qd67ZT8uPDueiz1Y/8q9r/AGvtvN2T01D6a89dfVO3ZVSLzF9ZO9H3AbPueFxDz6TYG8ueUevd3D6z3AjFe9CdemvLdUhL3+PZs9JQiRvVzSQLxbhGu8+1DpvRWrRb23tUq8tg+pPfaWAz3cEbU94bAVvpQ0TL1912Y9tUGFvPmEjb18CDI9dOuGvd7Ecr7etv89PST7PXOxuTxiw+u8L7KDvvnPM73YFQ89CjKcvXBJqL0Jrme9mYg3vdEboD23GbC8/NdMvTaPPb1VzlG+qi1XvYitID1M4SG+k8mDPGeTN7y0vQs9OFSrvSp/YD0Kb/K8FuedPJO7Fr20KjC9/gPNPeGzmT1WHfE9v1e/vfChwbwXtfi8yy4qPfgEQT6bo5w7OX0PvsaGdD24Tx493ziHvT2skTv8C9K8YUZDvWQ6cD0rveE9CpyEvVoRAT0pH2c8UzRNvL3Xiz2ibpe7fZipPespuD2AzSQ9YyAFvgBHHT4UiNi9fvIaPM2vFL3aF948gRUVO8Hk2j0IYZm9d+uePcTa6jmSTaw9fWP9PS4wz7wA1eK9bYkdvmTuuz1dPta7xpvRPfeuyrwo0ZO90s8mPlPyu73D3hm+NV+7Pc1Rrr0GQwq9W9hKPlosE725oBI+8X96PJxHBz3MOPg8jM3HvOCFGj5StlS+1/3BvXGi4L2k76O8xSawPGMnwr0IvV+9q9+JPOaEpzxG5Qe+/Is5Pc11p7yaTKY9H2X7vXWk/z3ivrS8sToUvjCIRT0LRK+9BDb2vf8hlz1jGZI9qlONPXVbZL0Ao2s97kDZPa7jwTvFXsA9GIRzvcJN4z2GliM+uLHLvcsjjzzgsE28N7RaPhNYXD0Badk9Su1FPS1qNb3uIQg916aDPQs0W71O7fS9wupjPb52Szx16Aq+dgMkvdNSgz1uIdo97tDsOwp0gz0ABps9Q7s2vHohu70LP3E9uLN7vT2WET3DTac9","JkjpvNx7QD1Vgum8nH1tvVA65T2XrQm+TS27PPLVnr33k0+9DRuSvK9CWz374HE9AcRyvbX8xr2BtpY8IBF1PGWTQz4dGc69p/PWvXwL0T0cckS+LxLau2KKPj2o2jU9ujubvOnmojsu8Ja9elj3vN3iQT3QpG697cCHvZ9JwL017C68e25HPMzgTD1gzCs+ie6GPS7jT720UQS9BKYePv2wqj2BLdI9nMCovVOUxr1J9UA9uavVPV+S3DtQ9029v1AmPa0rjz1fQ809Cx7EPSJk/L0e4Zo7QS5oParhzb2Pcms9JxrVu0NUIj0pPci9Z5AcvLbmcD2Pl5Y9WVCbvJVo0DySIky9S63fvWpIXT1e5hy9xRGGPJ7Bfr0KztK9i+6hPZVANr4xtAG+MBVsvJyOiD1e+I49xfqfvbjuabxQZiQ9e6DTveYy4TzT51Q9fw+Pu2muCr2q5k+92a/WPKEcDzzdlmG9BMhWvaJ/+z3bpwU9oJEhvRWxKj41HyM9DuWbvUYBij3hOrw6E5GcvVdpir1FGaW8RNccPf5ZfL3AKDA8YJbAvLN9Cjy9MEK990oGvUuoBr0lyDg9ABAcPZ/ZAz2gEDm9lpC5PXeXLbx8rq69cECJvT+RGr3JSJU90v8yvXodjj3Pe5g9J4wpPanlTD2x4zY8g36XvWsJxD0oi7M9rbrRvRYsgTzFbxc9N42/PFxqY7yUPDw+d/VkPl3BA71z/KS9QAFIvBye8rxk59o7vne0u43Kvb23RmC7EdGpvAx1R7zkicI8eh84vB2hsb1NjQ88cR7tPYRk7jyb6s485TeNvZiFhr1yf7E91HERvg7JUj4aoXs90OS4PfHy3j39t5W9+gUyPo1VLb06Rty930aAPbdjij0KGKw8ibD5vDRSqDzycB69EgGpvStjJj15O4u9dhruvbpAwruTRaY8O1n4PPTMST4MQ9+8OaKbPegK4j2Gb9m9XEJbvcK8a73KSKs8T+JDvsmAOr0MUxs+OiHJPYuRSr4lCry9","go4DvTs7Hr7xzgw9ugT5PCe7qb3bbBc9/DEdvkAiULyQa9C9+/pqPX20eLywJ8A8WeE4vtC4oLwkg4O9p5UPPmidkz0gP3q9HVtYvd6PLD2rPZ894EcUPgRpXD0/vVC9UwF6vWmsJrzadl09KV/RPXKSt73DG769nicwPWUeB70JcGY9YeovvRMyhbx4Pi09xOsfvdq7YD2cXXS9Fc2HvXM5hrxml1U95iWCvXLYbTxUcf699AyTPV+WPTw0Uci8MB0Avvh0+70SLpq8ksobvYfonj32E6a9UydfPanPDL1/lLS9nwIAPnIYRD3/kQy9zwZnvSm8jb1qYOg8rYjOPWEzCT1a4dI6Ax94vS95gD29rA29x8iSvW2ODT1QE969GVTUPIMKHD0m5qK9Ym37PG5rT76lo7Y9Dc6uvT/aVz3Efs29/yisPICFort8xgm+uUCHPaeIU7t5PAk96rKDvbEuXb3zJJq8mAJgPdmWCDwRLHm8C7rmPVnagb0M8789y6uJPXM0aLykcMe8qUfMPHD7drwCkMA8kdItvdR1ur3E+t697nH2PaTP8ryDkTU85H4Nvkfbjj2Ta8+9SeB0PRJpzjs3fsY8I1sUPvysrLxbKhI97d8DPJWr4TxF5iK+r/UwvX/hpLvZqpS9aYmLveA9BL2oIgS+YpejPaMeGr1CyFe7JXsAPUtDvTxNMvI7Lo9WPU+Q0Lz73Kq8nMAhvuBXPz4D63W9GR+hPbRXrLz5Wwq+cSDTut5xMzwxmRq+RsgOvaUqrT188v+8OWT0vf6lljxYcZq8ba7xPblcYj3Ba1c9u6XnPRTGC71XErG8nisNPsE8rzufbnU9hKEzPg550z3O3Yc9DZUsPPV/0jwviiq+4OmLPBViBj67Pgs8tCXVPOvVjrzCqGi+Q8g6vRXxZL0ccWM9LKPku4hBnjxcLUK9Ji+EPc2OvT1Buta7IbsnPOa9/j3DQ/W9rwkZvCeO5jy+C8k9srSSPLFchT3Htay8TYYcvS4Rij76aSU+","fLGcvVIeEL2zpzk9VloVPGtyeb2u2vq8Xa08PU5hu702dqE8uSyEvKtG2j0dG68909WSvRGHrb1xVjY9v8lYPZ0D2jyBqo09tXwVPuaba70mUVS9gvOAPDYoYr0BF6+9ll3rPXAjNb7uk8g8kS83PXmZJj3iwMY9OfjoPfqYpL2+25m9Ebf3u99JSD3/G4u+vXc2Ps/5PzwiFzA9OByZvdvcyL0IoWA8wOCIvAttl71jVTo9TFibPIV25T30LZO9ow8tPQenpb0u/I2+2g9dPKiUTj0G+fM9s3hivd5YIb3VXsy9wQ88vRgiPL6kth89uiQRPpZHxzwJzAa+RHjdPIBJRTxB/Lq9tkacvE2nUL35xIY9BFEKvj3MRr5mY9o9dDFMOziJuTxCSv298o1evNi7Iz5p1AQ9G8k8PQZTrrz7k9o9bKKHPQYLHD7jeKg9JuxPvUCw1b1I5hg8iXhwPkYSGT4+jR6+/+NtvdtXA70pp8C9hUuDPTZnPD25HjQ9eZv/vIvheD3EXFY9H4YCPSJMTj08ofm8YVnJPW6HIDzxsAI+kqxcPYDCoj0VX7+93u2jPYRhdr3qXvM8zo2Svc7xnj1Fq3A979hPPNGRjL2w4CI++HciPjTlkD33Mdk9LEq9PZ7aaDy2zQQ9cjFivQYHqT1AKWg9zRVZvWUJ5LsVmCK9Yn5svJIBVzyCsTW9xDb/vQbnsz2JdzO9SDa8vNf+qj0Gpia9+bfHPFL4SD1OBjw82UG7PTv4mT2Al0G9RTokvrOxwb3QkwC+8a/pvXtukj0Bggo+m+5bPpUhJD21oj8+mFVQvSkGBzwKgTq+G8S2PB+zlz2xTOa9PEtYPGsiWb3NyS+82vNdvPRUWjtb1hw+SeYtvbHcbL2p0F+9B6RYvTgnEj16W5o9737SPdiQhj3NfYY9COY6vCccpTyre8S9WMA0vaXIiL4bapk9BWn0PJo2sT3X1KW91eJTvZJwtr0DUQm96Ue6PYePqz2SSSU8r26CvkwOVT4a2L49","/IE2vklhG7yY7fk9s5AcvUBE3TzYOVC8ZJKsvmBxyz2D8DK+9g8vvqgblL0Dhq28WK51vcsbrj29JtK9e5t3vYqPeb1Bwkk9ZoqhvpNmCz7wYJq9dToAvkcyO7xGIIO9DM4tPcOvzb1RsMC9oCIcvY3DMT1QyRK9B9O0vZrXCz4V6l29eHFrPZN9cDsspcm9EIzkvdu+gr342nU9uTxVvb1AOr6pItm9W8T2vGVuSD7BQxU9U/HFPaSVH77IS468JmwePkuQVL3GflC+LCCavX5RXr6Mj/a6Q8gGvvY5Bb6a20i+Nz+APciuKbyOmyk99frUvETT7L3IykW+oSrSPRqbq72fsWE9T5mKvdmfEj0pyxs7eYxqvWiZ3LyNsWs9W6fWvGT6qjx5cU89Ov36vaBAMrxaEPm9L5hvuw0Q0LxPQto5896Avs8cfj0d+4s937+4PaqbE72T/66+VgNNvuJE3jyLBtq9OUknvnnlJb0QhXu+e2MMPPBc0Ly7n1C9GbP+PTVRoj0qrCI8/EFUveBmSL1Qoua9/YIfPah/DT4NoXi+3/ErvVDC7r2XNUY9sbQMPqrciT263ty9Re0sPKNejb1N5Uy9o6aEPZuL8b3r4oW7SdUWPtdI1zyTtwM+KQdlvb5epr5Mb469mqBSPWatnb3Phhy+TYyCPbGXkD7dji++qtEIPZLUjL2xKiS+p15GvipZlj0O2Bq+cqizPAH+X70IXXO9oewavahoUz4S/0+9yrn2PWTesjydnNG9/u29uYqWdT1zOXk9zX6LvXqLyjtRGCe+pB76vPUpez1WzXe9Dfm2PMwT1T07jcq7BuAQPqZRlb2DIpm9KpiQve6IErsVTac9qDdpOphzgzovDbq+eLGvPdm5JjztF2U9LcREvXq1izy0Y/M62HRhPQHixjycoW+8fUsTPamLOj3rWt89jvvKvfAZ7Dyp0ui9/Y+UPQPPir38AFI9cU9NvSqURb2Ecs091xIXO5yC1zw4zOO91csDvh7ZnDyF2iE+","6uHMvVovkb1L3l87BtPJvIQTz70CKPa9n7GwvCopAL7igRK+16tPvVOi9zzKqfI6G6Wnvgq6yT03gN29Gj1WvQIdZT1w/t07Z9ihu7uPB71qcLK983/rvRhwpb0kLk6+GJOHvU0F2jwJMg6+IFOru85CBb4n88G9gUwMPaVAyLzkdgy+XrQ1vGkiLryn8lq9FUfDPbs3or0Vs3W9QbkhvZptPL5S5FM7ZBnfPBHnP77cXpG8PZ7DPdD5sb1kPR685+sKPoOGrb0GA8m95SaDPTR7TT1jc+W8y33yO2CNA75GVqq9EsJCvserDr3nkCm+QeMWvvyf/D152U28EVzuPSw/aj3xgOa9kIGpPMJT4j2tq7m91PkqPk4aw720BCe9Xf5sPkwjTr4ybuO7XF3wvM4+7j1wygE9DYuvvccspzsFlKa9MY1VviPo9L1dKVC8rX0AvXFCnr3yJ6M9napuvQB2er01X3+9WrK2vNyuxr22CdY7Q7JKPYGkB77dn829Xe+4vTgHCryJp0w9/zpvvQ4wuj3W6+G9TADVPGzbRD2igpw9VkkpPvbnJr5TfCc+aQJlvSh0yb3nVXk8B/hDvdEH/D3W4l29sBGUvOVJGr2JwM+8adqyvRFJM77JAxS+HCHdO4kyvL3JNKi9iYEgPl0vvD13M2s+CaCzvYZJb77XAhy9EQ2SPPb9yDuuhly9t0hKPFxXHz1sqfg9LwCBvliNkz2xw9g90Fq4uwjn8bzMJM87po2YPbbIJz0saIG9EQftPW6+3r3A/Fy+BW8Gu1Rs971gbhM9qu+qPFrWUj0XiB49/V0NPZ+pXrxVWsK8YbMPPfcoOL2/twi+zxePvmmKmD1DGY06oGj7vKUNPT6nWDu9GID+PZ/BSj4yF7W8c53evAOdrryCYc89v8j2uzRJAj730PM8htkNvuVmKb3zeWS8oKKQuaQqWb5Xo/G9Jeu4vWhRYb2KTgK+CDOfPahUXT6BWnS+03E0Ppbsoj3rXOA8FbWnPgNFqj5hKV66","BDxWvQKujr2UESg+UvmsvbNGMr1ROnK9ArNuvv/yN7537LQ9B3cFvFCduj2fNJu9HjJpPTvE3D1/ByK9RfT6PX97vj0wFGK8eIGivC0Xur3L5xq+PFZwPZ0zkb1OwjK9GGeyvUojf708M5Q9dK4zvTUMSr1OSoc91bcwPf4kFz23cyu7LDmdPS4EAr7dJ+o8BCTpvXnlxL1Tt8e8Qv4TPd7Hn70t21O9KNtLPm0EPb7Xe7s82WwSPVXtir0qT/K8vGw6vG0aQr3PI+U8WAzaPZx4Wr7Uw0S++ds/PoWnVD7z/Is9ZPxsPozisb2gGyk9VCzSPeUUeztuvwG9w1DdvRiJy73oDB8+VEAEPlwwID2E9JA99H9+vWJptL06SB6+nccnPSowv70Ggy++IzlCPGSXrT32MDQ+KQMXPu+xrLykl+u80urRPe4MN73ENRM8suMlvkKuTz2WHB+6p7bcvQEcCr2clMM9/a+gPZ/KCz6zSya9aLsqPteJzj1MTcC9S5JdPA6Ojb07H4E9N/RkPv8SHz11wou8DovCvZuMIj6Ih40+izezvRdIJb5RTUC+MBIfOglcRr4BsTu9unKUven9Pb2p2vS87RbaPQERMj4qbHY77H8RPTJtDT7iw6W9Kb9NPYotM74rprK9gOVFvVeeiD1oy08+kXoPvZ/mDz57hT29KgYKPHbU3T0QIOu8bBchPuS3kz3NUoY9lyMtPAf3Xj7+aem9zGBHuyEULD3GEHg+utqnPHh8BL28ISe9IxzBPVRSgj1JsIE+6ZQBvvUABD74OSI+EBrQPWkYlL2vdVM9r1xGvLT7kD1YDS+9YdvmPPW3ar21pIk7YqxbPuW8Gb20IgW8udKYveAswb1WWvg89YA/vFftXj0EfpO98awEPhfFZz2jbTs+z7gVvibTZj13W0E91WV2vE201jyRcWW9TwYEvRF8Tj5TpZw94bOvvb3wID5nBce9ixX+vR8YG7514wu9Bs6iPJKZab1xDA4+Pa0SPmhRw70GAbu8","U8RPPEIT4L3JnkK9MKAUu7OCZr3jcWK8bYI4PbxVlz12HVu78MAhPbJGn70pAzo9ube9PFMKIjvLygK9W55qPa7gKLzyp9a8AdQEPXBu+joRHjM7WYmsvADf3LyU7Xk8etIgvOMRJL6V41E94dbPPegXnb3TMSy9r+60ve54jj0p1sw6OWk6vfECUL4LYSE+bSPpPZe4/D01dVM84MW6uxftyru6bSg+OLzcvRSftjzszS09c3hQPS2FBz5PHh0965TfPfGL5jwsOSw+nasVvR64iD1Xmzq8+MgEvdS1W726i4A9x2OLvEaimb3THXE9OmK4vQ8JGL4tk4Q+qHqCPACJSL46Tte9rKgoPSbsP73Pnzw9NIMiPmaBejwUD1A+KuGHveeIUz0gwMw97VY5vqPCsL378Iq9s9ZLvWPJuj1WHqi8IMSOPTUrXT3u43E9JaDUPYNyvL3u/Z28xliyvfmJBT7AAto9SydqPfL1gT4qUcI9hbm/PMZf9T1OQI+7dvpgPWVhYT17wXU9YE2aux+4aj1pFh+9CwxOvdcKgzxrMQE+kttuPcQnh70Djkw9rzyivfrrlT1I9JC8uGmCPOC2hb2aP+S9HAlEvX5OYb04azO9PfJ4PffkVr05+2q9lfBAPcSQar2caKU9ucVRPf3kgL1dkjQ+SUDNvNSnNj4Lk4m91b2WvW8Nz73b/K08P9X8PfAPYz1BHaK8tcKmPe09hj1lfO07JUHnPfmsGD5CZr89a4NHvfAQjz1M0Au9+fWyPBAK0j0kA9o8sO7kvbFtk7xBAKC9uLQlvtt+l726i0Y9EY3QPfWkVj2YDBY+lAANPQJ4Qz0KBBM8tZyaPVGSR71yCIG8g4GvPBv2Kzwr3Lw92kXmPOzCiD2sGwo9GrjSvFColDol+bq8NEC4Pf8SxDvrXDA9+nzEPEq9mT1zk0C9re67PQoZ+b0m4Tw8QXe2PSfDpL2SgyA9etcZPcYUfL0AQGa9+cdlPaigBD6SOYY950+DO7d3ID1BMMa9","b1gvPWMdurzuLt683MXOvRMsUb2w8My8h70vPnrggT2OIxS+xBLZvZqbGL3ACmY9Zos5PZW3bbxNoyK9yBHYvG6f87w99po9VV9CvvYY6bw05UW+XowPvmNZDb29D6S93UccvS30kj00z789nHSbvFKnYL3QjI+94ZnzvVR/+ruoe/89d/WMPUcwYT0JBVi9JZ0WPa6JKr6+g509k7huPSMAlztlfHY8lpbyPWPqAz2b11M9zUb+vPvSwDy8X4O95p+SvrFinD2/F5I9aO81PDssvb1XlOK9mkO6vQoSHD3WCOi99GuLO2exGj2CNCA+/vgIvV29bb77ZD27Zc4+Prw0BL4qP5A8leU9vUIVLj0n4A29mLOCPYKxJ73nhk+9DZeEvRas8zwDdqC9kJq2PbNS3LxWvYW8EOePPW6XPL3sRMI8KVb2vf9OB76nb6E6wSlPPi0OoT3r2c491eKrvRkiLL6Od+u9Cr49vPFKwDx8ncO8P2AbvlkyGb3w4z29esRYPJee3bvGmG2+1Nwzvrc1271gG569odzsPYFf0r33vRG+80YPvEti+b3ZkxQ9tVrZvG/lxzy6xL09t6pjPAWvlryOgq86TYrtvTMSUrx9kos9T0o4Psr9br0i/f88x3XfPVFI3DvMu7A9kdbhPOLdvb3LwYS9IAQpvN6sYz7asrM9kYX+OzQRaL1Znzi+WgqEPZNgrry+2mu9S2Idvd8vFj5tvgA94HvrvTV9GD4kK06+y2OgPUGi9j1RUuO7f7AHvWO5nD0qn6c9t0wCPgkfO7z+z/49WQ0OvaVjMT3ZBoU9qEpNvfmn1L1dhxc99XyrvTr5nL0Cli+9K345PR47gD1qVDu98y1ePe+5+71egJ+99vAgvvJ5oTvF3d+8KexjPl/8xrt6a3a9KDCrPaiplTzAeEA946uNPZGLZz3rJTo++TprPq4/obxA+KW9L7mRPXEQrLwhaZu8qRQoPbUTqzyPNXk96qBevdC7kTyV6sA7tasaO4Pp4byR5Bo9","2cXHPXBjZj01wna7BBzbvBMRujsvMZI9vcEiPRVywL1LWLE9KF+IvXCkYDycn0W9xs7zvYvnIL73wwa+uTwDPZmjjD0K87S9UNMRvmys17yFW4+9suNjvXjCKD5OUg2+6TKUOySndb0V1fy8gqrsPLfzvb21jDA+NNuovS7S6z24AyA+yARfO7hLnL1YHDA+HAGOvWeTjT1a0a69OVEavKtqnb1SXkO+d1l1vYPziz1aGHy9Z9PevYfqz70v0Ro9ox/XPAFpUDxO+pG9hVqtPUFwJz2LB729hBsNPh47g71xu1S9Fewgvn6kebte8FA+lNnjPZY6JT4GTkm9uIW1vE4eZD3TcSK9VtylvWNjJL7uFzq9aFuCvYr1P70Fp7291NkjPMuLNb4szaW9mEMlPatZNTyJq/W8U7WfvR0tCT1pF9U7xXqcvc1+KbutmcG+0ukYvQDfabyB8mw9rmygue2bIDxsBU+9CH4BPsx4Wz1r1Ra8tfAHvl+b5D15QBQ+h0WWvY6E8zsnwAY9Z1nQPTEqpTsqFIY8eLGZPVjZX76GNK09WRHLPtrDrjWj6RS9Ux6mvc5aAr4kaS49t0w4vZqaBL7F4oW90IS3PDjYTL0EOmy96ftyvdtDFb2mNEw9hxXOvYS+B76/csu8g1tMPhmXUr2oVFk9XGXaPTqdtbvFlnK9OEflO4s4YT0S2eG954QUvW+5TzyjYQw++k0XPH/kPT3JPRU+yYAGuDYWkT3t0He9OZqtvU0VjD1yZMk85jnLvU6PwT246hu+cvmHPaJOUTxjIi29Uj8TPg2yPTxNcQs9Lj/MPWjQTT3IMqa9PRvtvFbv8j1UeS8+NYq9vePuqL1KjOS8kg2CvcmcPrtWEtc9kAIQPYo5mTy7T0C9m48CPu0/jT1wznQ8aWtuvVjiID13XQc+tG3kPF7R670WzcK9yjUqvZe4QD77Kgu+Hj3OPQPDFr2Ly/u8dxqgvYStgb2w0h89mzUrvh1Lwby40gA+ub4NPi9s0D4mTfy7","mIrPvXbwWT3IrZS9SpM+vkySk71D9ic9mAu0vRRC4T6ITIs9nviUO7eJRb1gvCw+sD6XvaLLDj4+1BE9GeHSvVLdbTxfed+8B4DyvAj4HD5jpP29ia4gPoo4gr3kqgA+Z6WivTo3LL0B6C87YKvJPNdGmT0btk098ZeYPZ8lvr0L9RS9bMuEPWU6Y70bi4g8d06yPfTJBT5sJfA8JgO9PIT1gT6Z2fk9Bo+avSfB0ryrGME9pBSOPGgf3D3o8+Q9PJNfvLHtqTzEkhW7q8qCPYODHr7PhB88A8e3PZfJJ73vdNI9qFgFPs8QMTzh2OW9/+F7PQ+JDz7AQBK+7WYLvYyvz73Bxy29Nhm5vP/LBr7nt1O+neP7PYYKrrzK4Jq9/TkLPZxdXj3+9Qc71fW1PQTeYb7J1zC8NPBjPYh4tT1ebJs9ZWiAPhwe57yjEqC9FZKXPfGaVT2d+pY9TaMOPp94N71f6qq77N2tvYS9SL7eJIc7J4S6Pva1pjyMqk6+PJhfPNHZir3TEqc8pzFQvbgiDj6juTC9a+OmPaX8Bb0xz8i8/bKSvHThiD220ky9HWodPQfWuz25UXw9Z+lPvKwVsL3OIk69GTMYvL8GBD75Sia9BCPavBt1vbwF50Y9/WF1OQ26jz2lo3o8n2GJvQYXvr0F9js+qCK/vb0ttz2NQOm8fT4IO4RmrD05zc87rWVQvqCvgL0rKRe+vs3kvI8I4z3cLCC+O1oZu29rMz2C3SS+uLn8vS/pDD7wOLE9LgCOvor1VbzdICc+I/QBPht+zL0if+m8rOkMvdplKL7oxPW9ALYFvsBw/jwM0fM8uBOTvB7uET2g+1S9xbkMvmbdIb60Rsk9ZqvNvS7jiD4Z+Y09BYbsvcLtt72BdFK9x/CAPTIwNr78CUa60hUsPgNNKj5CXfG9472uvV6Y+Ly0Bjg+XsEFPMCKs70VAxq+JYRGPjaQor2oMCe+Ho1kvt13Br5q4JM9L8RgvEFnPD6exMs9VaoRPS1PcL6yEhE8","V4z2vLKN8DtNaoK9Z3zmPXqa9TwA8Wq982BSPnOV0L1qe508FEXWvfh9I77w+gu+P3/Gu4AMcr1hGPA97S7tPW6j/r1JOCg72qqAvhDMjL2s10U+VKFmvd4Io71Jn5G9LO59vZMrsrtzVow9IeKQPXxioD4Ugl68JDj8vOtGK75Jb4k9r7HcPVRC4b01Uec9rQaFPoG9TL720eC9P52iPsEBuz3Ol7G9C7WQvcw+vrxHywq9Lm65PTeAJ70EUY69DBYZvgZopj2ZaWO+sIluvt9frb20DMe9nUpmPc9Fzz3EniC8vZIpPmsvdr7WYVA9+ZdFvoDyET5eByE+yHdXvooBSDw9REW+qsUlviIeq7xU0tu9ZSRkPXFoEL4tNNu9qCVzvbmrdjyz90q9LB/VPBBF7r3+Ro69Tc0zvG+rCr2VNFG99JoOvhAPJLvxXqS9bLDIvFSFD75AbHQ84CWKPWfUTb3yg3I94bDSPENTb74SVIM9FLpVvRF23jwifF095cV2veCfBr3z1jW+rOn0PcTNH72KB4u9f4bOvaVXH71dA4+9R/5nvVFbBT6aSSK708LevUGBHb2bGZk9b7oQPlFrdz5B6DM8Lp29vAkyr7xsnBQ+iFAfvmaPN7zvjQY+70vlvZhIsj1H5BE9DOOuvD2cHD7s5Sc+nKU3vaz2mLyOQ5W6HCCjvV53FL3zxZw9+7isvRSPB76zCeK9Eu+UvDisj77sZ388gMFXPaiNYr7QwiK+Gmhjvj2O2L3F+Wy95HLivc4jiL4gvb49Zc0fPn8Gp73kZQW+2dfKvUJytbs0cYK9ggQDPMQwiL3lcZu9drkEvlkKMT23lXQ5bpoDPifCFby8qoM9JkACvnkO/72XPnu+PN83POlIjL2Y1cc9efPfvcqLnz3lTgW+MIS7vaocwT1UOyM9Wn8Evioisr1UMs49FsyXO+SrBT5xpNY9ngL6O7Lmpb09bjy9ihyPvgSyKb0uLr+9cVqlPYFsFb5OoRU801hdPY/gLD2Wg/u9","hu/DO4hGfD2PuqG800l2viaBG74f6Qo+7R9HPXjJsL3i5AE9InLGvVlbVDytjgu+oKSWPWTgszxvaeY8/SKHPUP3pr1xH0A+iMrsPDrzS71Wm+O8tUC8vX8iR70Wnrk9Y9lmvb786jy/0N49XyaRPRX2DT515V29jITTPY4NgrwNjN+9wElkPYp42zsC8co8+Ss6Pv2/5LyYd7o9H+YpvrRg7bzKzRw+QBUAvpYQUD5E/BW+pdXVPQloMT3aaYo9nU6dPQ9JfjwrCOW8x+pTPUxKEj6p3yK9z4h9vDdft70dJGy+r/XYvRWRWzzfUoE8chkcvdc+kL1S0hE9+ZcFPhkTAj0+i169+AqPvbjyRj1Jk4K7vgaMPRRdqb3SGkc9c9HgvDE8Fr6G65W9lF2UPeCSNr1AtME9sppdPdI/ijw4V5i9WFcxvtLCC76jLqE8NErbPEFCxbmLuGU929CaPI/6Lr2S7gS99r2fvYNhXL3vMqW9iXL9PbeNxDwHdN69MpyVPQAYpDz0pR++wkYevoPqTD7Q/Bk+rATaPefLi76Q4M89L+SIPYZA1j0juw4+GpckPopQJr22+8c9+uxevR+eyb2pmZw9WeMwvhy1Ij7XAKe8uLuQPd7+K71ifBW79PDQvVY4Bb5PV3I+dOtSvVpUob2Nbvw9FklwvYeVk72aoA4+TUvBu2AKk73jj8Q9JqnANzVllLhM7IW9SDsMPsLuOj7ejyu92cnnu/N5hr0CR0i9mFMaPu8PIL3zOJQ9g+TfvSpCCj10pj093M+FvZhuD71081a+Et6aPfNw/L1Ft/o9Y8+EPIL1bL0b/kQ9TEuHvE8FW71xpuS9adQ5PcVXMz1xiDG9Uf+lvTsWWTxNlIO90+W9vWTW6jykLr09XruHPiwsoDvhuJK9NDZxPdgqij2SOcu9PyUzvoPIm7yZne+9nw3XPBP3pDzG6ri7cxkcPIPzvLvGHnI938Oiuw8bnz1fSAy9vW7vOuWBKD35i2U9UsJJPWowI70QaQE+","phZHPg5XKb1lUa48UYcTvntXfT0hnqO9pa3svZXK6Lk+rpS94b24venCVr2JU++8JF6tPkTt4TxBygW9k0AMPfZrtbwRVfi9Ta/3vY4vQj6F9MW9p3CSPI6rzT2IAC29OY4dPWolOb2dGGw9FusAPtqlDzxN7OE9pgEDvLOPIL4XwFu8TNCOPeZjZD1tc869Tp8HPhN7iz2qV5G9/4IdvtDGIb5aGKW99YxbPUmBGzy2/wy9CpDrPVAD57wm4cg6mbNTvQWqz73Q7O+8e+PfvGOBW74fA9s9o/n1vKNML70dR+C9VWEAvmNsCb3CxbG9OIryPZy7qD1UCcu9DFS0PYc8jbx3mgA+1OjqvAyz2j2Arwk9K1tuPWlFJztXXrw9aeWqvDEWOL6Hsva8FZrRvNh0FrrddNm9scYAPmJPDr79yUW9BlYJvml7/72xlC492xycPYRQib2iiyQ8ktmXvVSq7LppFto80Sj1vWkCCLw2Lwi+vUauPe0+xj30W6K9pn6wPJQRkD0A3jO9VZAfPeXAHb3qo3C+EPI0vDnfizzAVfG91/XUvNgDMj4XJs6694LsvLXQJD236a687/Ghvb/8j766pcg8yyd8vR8oLL3+fKq8LQYjvqyVPzylhF29A+NQPehBDD0RGwu9MF7jvakON70Ivgo+h58XvJpVe70Y+wA8J/iVPc2WnLy+pYK9dKwOPQggw7wCq6G93N20vQn53DomFxE8z2wZPYdDzjwj59Q9EbGnvcWzhztg0oC9rnOxNnDG7L339mo9jo2cvHRlCr4CVua8ESz7vW4ATz0mX6m9wmmZvfXlmz0jfnO9FgwrvaDZBr3AViW9USbbPbjdXz04Sus91c0jPhYNDT4ZGvO9JHGPvW5i7z3mOhc9UZNgu/l1GLsDKkm+6tUrvX0Dhr06pek5thKDPfoIDb2YX4A9n0CgPEwWH7y4APg8aV5uPWu9YL219ZG9vSKfu5iiE70ffYA+ASuQvZAzGT3XUWg9RGsqvViq+r3nQxg9","QcfbPXrHMr4Z8dQ8btczvajdirxGdfQ8Ji/fvdT4SjxAC+q96hVIPGXNWz0mlaA9WsChPUJE3T2Eusq98mIlvuaLhbwRnj+9GmHkPFMkEr4So9u9SPtPvvhOqD110WG86LENvZxdP704Oj29VYqDPX6ncb2+6rw96wGjPdjSUz0FcB49Phv1vZmGNb6SQo+7xSuHvNplrjv5i5A9Ns3Gu8fvBL7NZYe7RvXYvI6m170964o9ScIlvXjAk72htOC8ihj7vEDnhzzREqK8Wcy8O1A1Or3ms1U98vc1PSAbuT3vYM697IKBPBahOz3v+vy9qiUkPVv2Yz028mE8OphtPf6m8btqaL68zFydPRDTNb3F3Cy+tFzlvL2MoD35+ai7dWE5vRqyB7s5DcA9CNklu0WlxT2GsZa9EOGiPQRgDr1rVKe97hz0PcaXGDvRlQs9+hMQvnUqLr3tGAs9l2jfvN9ayrzfQbq9QpjQPbkUNr1wcM88kCjgvU2hHz2di8Y9UGPUPIr6D71mTS68kFTivADjH75ABpo4I8vivU2E4z29wPi9Jo1OPT73xj3EDq29+l7bPV6uhb33wjI7K89gPKLPgr2110C9Pi+oPXM39rw7+4K9ZgiVvcD4or39t0O9xnPnuzIqd72QjXI5Fj6mvcufT70Ffj28S7Oqvad5nb1qNMM+x6AUPuNtyj2w//Y95FcRPn4P5LwreZk+qkgAPos1WL6e08c9eMjPvb4lQ7wWrEe8jxufvQ4wbj1RK9K9InGWPXd1NL1cx/y9QCRWvsY3iDwcoVM+EuCUPXr01Lwyses8Qt+MPtcykL157wy9nb41PU/b+D2GTYe9w/5qu7pN2j5prkw8CJ4HO1eTMz27v7Q9lWbhPaowQj5PfwK+GRGpvcexYD2NpPu8pMmfvgfSVL31kKo97io1PN3jJjyGxFe+zqURPUKQAj3UusW89++wvu7RRD1utLk9Hx9RPogkYL5HTUE99c2pvNDAU75N3nO+AH1gvXPzPj6nzRC9","Z6wRPlDUkz00Yg6+nveJvaLiGb30Svk9KE8ovfdt2T0AJRS9ULlrOfkowT05xKA9Bs61PV7n77wCR1a9gmzMPXKM5D37iUM+Tx4EvpwApbyv8Ey+HbHqvYnfBLvz7/s81oxsvY4oiD5p2Zq9HsoFvJ7Bkz0nMCC+Gy0TvTtnWD1hYHe98UcWPrSPED6fdPi8JtbDvdWx1j4Pejq+BNysvuJsiL5DHbY9UUlmvtFMJ757awI+DR0rPVfXBz4X8ka9k3uTvS+6/j2Q7DU+kvR9PgkPlD1rcVI+KsnLvQ4HFz4oF9U+HdOfPvRZ4z1Qwns9GEUwvYZfQr2mwfy7ExuBvqk/j77K7Uy+wZxQvRn2Fj7sNRs974g/PiC0KD44UR+8Tcp7vCwRo70eM8M9+P/dPd2w3j1v/Do9X8TfPkEF6D0iHBI9mkUrviukkT6jRr4+73mHPnue+jwcHv09QlWKPclOlz5fZiS+UDYovSiiWL3xGKC9eDo8vYZqWT1oPbU8QfnEvDmdtzzXyRK90TVCvWW35T7DeHa9j2YtPVwoiT2y8yO+tNyJvVdvAD6dSLW8gT4FvjL2Rj3MsRy+4KSJPM+/AD7khtW98/WaO2kY1LxN4bC9utrQPlNI2j1doWg+l67qO90i3bySVQm+cFDzvWdDZ7xBt9C943HdvBpx+z1e5lw955KWPT8/iz5rQVQ9Sz+ZvWxmLL4oLnm8jOrdPcvkbj4fmJk6Z//tPlmpTL649yo+ZJMBvXU0ND38sfQ8HOOLvd1YQbuRbZ+9FYLwPRrPnj0ScNw8I4ufPs4Jaz1R8Yk+8KC5vQviTT5H/JO8wOYQPejI5DvT0ns93em0vRAPKD5HPda9zIb/PI8zWz5ujfU+PnsgvHWa5j1byqO9Vl+zvFGlDz3SVDe970eZPi9et7xq290805TtvbxmvDz8N9A91m3jPd/kqj7VCAS+PQpEvl65ob3YRlw9JjbtPfVn0L3w1wg+naxDPVC63j7w9cg+/HQhvtKAMzy3Sgs8","aIwUvf6T0jy4c2Q9F9+OvFwS5jytncq8fssfvbL5n73cnj8+2novvqT/+70ckNs9oG0oPcf+BT2oHh0+QkkYPBHN8z3U0ZU96FqtPvwx7T3pRTQ9zOkAvR6YO7yN5B4+C4AvvZRxAj21Exu9P7MCPElrDT7rllw9Jo4LPmzpBD1dbWS98U+sveQyvz2t9KK9hvCvvbq+wTxq9E082HBIvRMMD7yICEc+1J7nPeKLWT4KYcs8aAebvCaBmL1UwIO8uZBMPTVHC70t3l89Tm9jPf4HuTxtyaE8qPhhPAzpj70/v3W9tVEkvcDh3L3GEm0+ms+hvRm2R73jJXe+64OcvBSSpzz6EXw8I1ihPMy5p70Qaq+8ZGIGPVUH3j3SRvy8v808PQ898D2PAUS93hQdvvNUhz1zOG+965GwvNOTlD1LDs27fcJDvXBjHz60v+S8yHHuPCPkNr3yAsu9es/uu9LzfTt26b+8R5d6PN2cGr4pmwi9eYbTPVPlmj13WSu+HBXgvOZn672cJqy9KgYqPUf2Kz2Q2eC9g8OkvdpArjwISgM+vgXjPe6rF77JAwi+/lSwPZDyEr7nwa88S6xgvQwJxT1RNi08PIx9OxuO1zxWmWw90k/zvOJUrTxCI8k808hKvHE36j3GBGM+OOTjvbuSmDrWy9S9ijDhPg2rwL17oSE+GN8WPViigDsFV6q9xyZxvbmo0j1j2qC974+kPvZ3IT355Aq9+SgOPrpTQj0waTS+5ai8PHat5LyOrHe93vktvZWAhzz8q6Q9BnUyPHe1Kr6uOyq9gE0gOtAxJT6HG1Y97/uwvcTxfj3MpIC9P8MKPXERtDwKC0w9qYCEvawq/DyOEWM8YAfGvJ2N0b3fXzy9XWGvvQEehzw6vxi9MnmSvY9XV724zKm9Fkn/vd1PVL3+kbY9cDwHvZOObj3mQCs+Tc3HvXhcwTuG+ws9xS4DPjprFb5LBfE9rKwIPGC9mz3yRIQ9tPmovI40tbxNV/I93LThPTBvmj2zvaC9","hywGvtnxvL329v48Vx3vPDwD4L30jwY+pFP2PDGcO72fV9i96RGKvCMZFD7R6RC9Cdl4vjaBvz26BoM76dUpPW9h4L1iH9M9uR4svbFOLT2cBu49s7yfPMpfDr6tObk9napYPRtdRr38GKg9SuEKPez/F71+VY+8Wn9hPV81Ab0xldu8c9hfO4zwoTu7bX69UkCEPRv4Sz0uXvc8n8adPczuNb7W3pI9nH31PYhj/j3miIm93M8QPuIeBD3FM129PyjiPYhhdj0406K+tk5rPZ13Mr2kSSy9bH0APqb+Rz4pQXW9yULZOwKdJ7w7oLY8S/7MPQGkHz5ecgC+TzLgPUA+Gj1ZF8a7oyx2vZM2jb2K+/09eYkBPsyC5Dzf9Ps9H07nPXFKMz34VLO8jxo5PQvmhTucDOY9MZArPYTxcL0++oQ9BFo7Pq8ODT1h8/A8UicDvRcpAD1E9U69XxeAPcP35LxuvKM8jsSXPDaGLD0BBtU9uYUqPaeyTz2ltto9d0KGPTrOXL2GbYe8chGoui5uqj1ZRSq9kIVSPcaxYbzh5qQ9NhrqPaaDortHnNK9JKpWvfnqzL10nLs8ujRIvR/xRj3WUdQ90eyiPRk34LwORCk+VnWDPnNrc7wMUbG7wQlHvSESQz2DFaG8CdGQPcWLqr2JZFs9IN+zPOWDNz1ielA9nTiJPIKUKb1qkN09CFZmPA5RSr254L29uIfrvOpByz2n5Ck8b6GCPKffEbtZdha+OzLHPQfLpDx+mHi96bRYPY2Xrr2WEt08CZ4XvevhAT3gvMM9jHIgvZP5Tz0/5Yu8kYCNPSfMGD7rZYY8k6h4vLeNsj16NRW9k0K8PICJLT1Ul1A+Olg1vPVGGjxMw8A8bvw8vdnAtrzc34o9ZuWGPNIlTT7x3BE9qBH5vDg2Cz7TGjo9Eo9BPdsn6ryPDAU9+6JJPFsSLrwZ8oY6nLerPNVLYT3gvH89/vAavXltiD0pVAU+hO5wvca8tTv5hyo9mrVmPiWj/D2OOnu8","JbkIPQciojxroye8sbZRvXjZYT0PBEs9W9wjPHwRJb3x8mY9eYOLvZNO0zx0ctE9snUAvsm6aT4/mY07ZjMIveKZrL36C1m8lvqUvR4vg72cC9U9EV0MPri8nr0KET29YOaePcFSWr0V5jM8UcKWPfWKNL1mLjw8+KuKvU+3IL0EROI9XFiIvGNnxD2WV9A8RG4mveQ9Mz5KFTy9ifuhPeLcpDxucSu66Q/fPH2BrTv55CE9UE8yPLSY7D139sk9pXVSvcFruj1kKma9H8SGPZsb/zmJt4S7v1KlPSY/Kr0Bwr697bTTPZeB7TwqA0e8eRyevNg2LD3bsSy9nVGFvHQ8GT2erNm9G9pmvawDGL2e70g+82XEPYqWszx8DPY90+/iPDbqqjvifSE9KY+UuXdjTz2v6yQ+WF5dPZ+Unb1wiCA+bTjsvNhtSTtKrVq9SOHePePNqjybGiy9ghkCvbPdwr34alc9OLzwO14JyzyWz+69EyQyPJbZ5T32qLQ9oRp4var1RD3PDIU9FFDlPe1shbx6/vk8yd6kPA/bWL0Ydyi+GoMpPDjTAb2PJJQ94Lv0PIeZUL0Mm0Y90fajPG+Oiz3f3ug91valPUAviTrIlxQ977o3vGtimT50KOq7v8sXvSH9oj0tm6S77Q8YvEIVp7s61oO9nXHwPBRCSj0uoyi+OUBgvaj3HD1mOoM93nQLPlQbDj1S+4Q7GaI3Pl6ddryglTU+woV5viqFOj3PohY9VDvDu5QVLb5Y4mi92ImgvacvXj5TuuW9CBzavShIxT3bQqA8uQszPbCr8zyl69O9iCqtvKEMDD132y69pjO4OihTZL0n2X+8EqAbvSEEKztJ8j+8S1KSPSwEzr0lpFU70lKUvYTiGL2ghA69GbqoPVvsuL3QEg8+ctQvvGkDBL7fYPe920lbPUiXB7zFB4K+o9ETPQiVUz0m/R282lRtPsYWKb3tx8w9nt4HPndSQz2+pRq9j3C7vWX0yb3EAUO+XkF2vbaFBT7WBKU9","SssEPlOgvL3JKC69KuqrO2GVJTvS8Ek9SIO0PSrwxT2Q/+e8e4wIvGi90LxO4Va9cbrTPB7rnrz+Esw7W+GkvbBeEj08jiU7lGqDvvQyf72djRe+cy6oPfiiYT1HgxW8gUzFvdf0sTyL4E4+5RejvcGi1Dwt2+W8Cq0wPfDNNr4jxXM7fwI+PfCPV70Ueq68cox2vd5Qh77ypBa9MEWHPYRjKTw7/sW8mrNjPYyrrL2wlOS9ri6rvW+r4D38oaG7eDUJvvmtEj7AZ1y9VfMCvXY7Fb43pos9fzqNvIEEXLvR/Ss8myWMPfRUZT5Q8aQ8wHIevkQRC73h4Ve+Fqu0vIDrhTz7zg+9k7WhvcQcB716HpM9iw37PTV/Vz0dOIC+SG0RPigj1L1NMYq9Memwu4j0Yb4lb/M9Ra/+vFK0KD5WeaC9482/vN64vr2CcoG9oilfPfTRyjzkbkw8Ws/+vVQjGzzzhuA8vf91vZDj1zk9G3684jj2PCFVULyZSdE8/1bxvBXq+jwjAZk9Lu4WvqOjnT12Go69aSOCvXo/rT3juUo+tgRmvSKkEz3TFI09+3vEParyfL2f2fk8X8OJvRh1qLzKKCc+rUPgvHjl6T1e7U07VZ84vAQTmD3YB/u8UMcyPTkhabwCA649+QOKvMvKjb1d5ZC9Kv63vYMIvT1T87297X4LPYTzar0c2Lq9v8eaPMFq2T1ovZk9UJhOvcH51Dz/YAs8ZBKmvGy9g72kZoy+eWylver0xL0OBx69JGOcPYFuPz0cuZQ9jSd6PZv6CD4vo6082IrYPa20UL1k57W90vlAvf8xBT2zwFQ8i5/JvepjmzwIEoM6m4MjvnjHPDyjXeC7UqFfPYMQQL0XPpo9e+pbvM0lS72p91m9uT2EvVtoLTxAw/u8qNMnPWOMjLzRGRK+z/W8u0LjeL1ADKY9e8MHPhC53DyqbPU7Hn1APaqkujy6WNw9YHHdPJNHR73CQyM+lc9SvYbqTb79vL29rrTHvW7qSj2C00c9","xYfovRK3jT29TJ294zGxvAUfxT1SCRU8ob05vtbJ1j0W7NY9fR9wPtBbXz1dg9e8xEfFvWCMnLuOpI+9Bsb/vfbvOD3hxeq9SoWmPsStnr4sRa+9PpU2vQ4PWL1zatm8Y5nUPTI9Fr4h8cc9XBJdvOZbybxLWGM9Qb30vcRDQz4JBee9cnQcvQPInj0mxby9NBYpPo+io7wsB4S9fL08vb9/Gj6HyVs9gQihvVjDZr1a2ay8kGAEPBrNkb2LxRo8OK1xPgI+rLtlC9+8CW9qu/q6nj2bi4O9JM58Pefyh77jdVi9GyYhPVtq2T1eZ6+9/apAPqUU/7t/Lno8okTzvI8Jyj1H/d49tEuYPABnkT2d1cI83uthvI0fkb1dFdw6X58qvYYqfrz4TdS9elkmvjBhij0m/8W99iiNvXQVETw6Ebm9SgpLPXpkCb5yhoI97QlxvU6NKLms5iQ9K3UGPr1bPLyc6pE9r5OiPA0QRz24x4c9oSvcPFcNEDyC2Ni9fgGivQtKmz2T+yc+3i3uPX+kgT5hh4W9I7+TvVcmATxxUAG+Fd4HPofhNL0B/E28BfEJPh4LYL2CcfQ9+lvXPWzDBDxxFbQ88I4yvlD4Aj3W3Xc9f+LnvJ7wpjzQH4c9oYqivYUJwD0vBHY8PX/cPV+Ttbp/bMY9lwJCvVQKpbzFIUa+drnMO8AoIL2dhB49JGUlPgt1A700Nk4+8JKzPYB0Ab4d6hE+mD+CvcgHtT3okI+7VyrJPXOPrz3wcP89a+RwvbVRLL6fOc69eyK6vhk+U754CyS+hxMnPBmnN75A5hS+Mpn3vNhlhL2cL469wSrnvRVRFj4l8my9Ifwjvc1SkDx42FC97imEPU5rlz2k4Rm+WgCrPUNtUDzD6gW9e454PcXhyj1fFXY98D7svZJwjb2vOCI87GL+vYnPir0QQeA8DMghvp4eGL1z4AK95k/8PCgwALwLkVO9RSdJPqvcEb5DBe09Pj3lPPcHgL2F+I+9Z4rlvNhQHT2Jwfe8","Vi3AvcWt+rwvfqW9aNiQvUWr7r1a67C9tuABPjmAwz2IL0+7tZwNvTi+KL4N5eY9gNeFvoOwOTvYPXG7UL4PuyTL/Tz07Ka9nn6VvQMsjTxAWUg9jDW1vWnldr2vrim+RMY/vkAwoDwpL6y9/YYmvfnqlL1duCI9tK8xPYxE1L1uj9S8g3laPb6MBjyeGse90UFcPbTuYr1g73G9/WwIvaDjCj6+7/Q7mCWLvW9SJj5i+389eX49vbKqy70nerc7BckNvrtIF7zFHac85nt6Omo5/r0W1Xw9fNLyvLkA/r2ZEiO7viFqPWUlMr0HdhC+03rRu6/ZBj5T8sw8q+JKPbetPr5iHqI9KKfDvTNfMT0pgR+9+lAfvkIojz4z/wY+5PaUPUAjBzuCmde9Vm32O/0G4TwQJm69rPDKPdD4Bz3lclk9DSWjveLe2L7DlHw7+siGvJrgSb5vi469YgMnPdzKjD1/0By8HVQ4vZtdfL2jsig+tirMvGYkzb1mEbs9WuYOvmwJob0uGgG+YgR7PVcOTj6NjfA9pGwevVW9Xj4kx4y9f70Ovn3B/D3cxbY9Dt5JuzNKhr1ooYe9Cx6sPRpPOL5zd7o92uSDPakOULzMEOs8j/aNvW6qHb4uPjW+F/GtPFZUHz4ni7a9nIqCvgSyI77w2nm99Z4sPq77Dr40SMs9nP3hOXesFD05zAo+pd5/vcgps70gGhm888lyPn/EAj0djBQ9m2jvvFEOQT03Vpy9WW07vfaxmb09ooy9HKSjPS20Gr7A84O+jUyBvS+xOT2LA/s9jtRMvSxk4bx0B5u9UrnAPfawOz4yj8e8HR5DvdTqEb4w5QO+WtGqvddCQL1md5O9CasoPv78mr2riPu91ikXvmLnFj2XUaA9Le5hvfIwJr2jhPs9wa9nPDa8Cb120C43wRqEPdxWar2zO/q9X5mxPcv0ED3WytK9AHo5PrnD/T0OA5688FYgPJZn1T1Knxc+7fY9vmD8K725VpA9BIPRvYaAgr4ItWk+","ApPbuhyZ27xkAV29tlKkvbGITjsSwAa9Er6QPmmeJb6htWg8et4WPl0NWDy9hIo914WLvhq0Ez3YrRO+ipMBPTv8qbySOF09kTJ3PVZSQr6O8NW+v3ZFvTMtKT2W8to9yZQjOwMmvrxNzBM9C5WAvZbXsr1JMka9vSslPrQnQz3qpxc9CX7DvdkFND4isb89W8a8vQOc+T1uAC+95Sj/vPJK5z2M3ac9WNoRvoV/BzveaJK9OcmVO3oxa759RlO97lhlPYbCyT1BoQ27ngr+vE8xnL068Sm+PpZCvSzHyL2TJuO9mEzlvLc4jD2X4dS94j6dPDzFkb1nZpa8EESwvbx3hb6DhJo8n/lQvoZ8iz0bmMc9t9CDPs9lez1NVQG+yvravZpyprsna948iZr7vN6lOL0MmTG9ne6IvX5AfD2FCwG9QHaUPlGQgT1WZSe+MYPqPKECdz3mTya+GOCaPWx/hz3jOaq9glOmvB3DRL5KN4S9TvhOPXHTiL2q6FG9ols/vWJKvjwjgqW8sVYvvkejvb3x4Ew82ZF8PdDiIz0ZoRy9iAKIPfzPAD3NmxC8Rg2kPcqhaD0ICq45BmyePIYCOjxDwF29XO1svr+ChD1PTSE9yq94PFyjhr2M47Y9x3WDvfNYSTxCw7E9quC7PQ+697zMK/M9lJvRPfzSGruSU0G9j976vUTccr2W0Ec9YyzlO4tiM76Kwyy9TzswvtdYubvbRoy9cgO8vKa0iTwwhEQ+sxe+vF/0jT47dUQ+SS9fvmMCnL6mmQk+um0VPbJT9b3LuBa8Na5xvSrTGb6HdVy9miUDPIDvOr5qzZO91AYsvq3Yz73Rsas9LXpDOj5o6Tw34yE9rtllvQfmqj3VvYi+qSavPA05dL1RQE69lwMgvqsxW7ygZFS+0fyyvPHoIj3E3uY9ORV7vWMlTLuWXo+++dZAvYRnmr3Yche+FRu2PrB1yj2nSNI9lUQIvrmQsr0NfXO+oZgWPSwBU7xphiY+6jNSvVqKubzVj4w9","ugQnPlhOOL3B7q684LxRvYSA4zjylqI9WO80PiF/P702dK69EEpQvv/qWb10jDM8wnapPT9M7j1s5Ka9eRe2u4nIXr7mEOe95sREPngVmL1WZ0M+KJaYvS1ZXzui33Q9WBwoPU6gQL6OdqA9X7ljPRqZmT6oAEU9NIbZve3twz2/oOk9yea6vZE/Oj6P4FY+y6C+vSE1xT21+Qw9BmKaPrj7HbyyOiE9W+S7Pdlylr3Swju8WwTSPMhNHjzDGh++nmAHvQtO2b050do9eLe/PXYwID1cK8K+hEicPQtbb72zNtK9eWbHvq6xIL7+EDG9r4IIvjebU70Kb9q9VvAsviateLxhbKI9sfDaPFXyhj1QZ6Y81fNiPKcV/z38V889CriOvZPjybwKRNU83yozvlIPk7xtPYO+7/iPvQi83L0ZpIm9G5bkPYspjr3u02u+tBHKvX4nnj7hVk48kQuCvUIVer1CczK92exUveOHKL4GC5I9ZS40vEB8KD7JQDQ7E1kbvKyv7DyQKS09GzIPPWlqKj7RgKi8Q+5CPctz9b0GqHm+GKImvXN9671lD9O9gutmvjEWcryhu9i9Zj7OPdujkb7U14e9ZphbvTis2L336/y8eTXXvfTMhjyD2aM8e5APPrxOqDtxOqe93hQXPZMWDz1iszG+lF1/PQmHEL0N6YW+TnJJvfs5AbuJoCE+g3QCvn5nhL6UBJC+pNvRPD+V4701lz49heD8vUIBAL41AKO9wSJovTsbaT2aImS+HatgvqBVYb5KFGq9xBQdvjm/8L0Dk5g9EKsbvp5FoD4ZFIm88NKru/ccAb7jXZS9oRQWvZH2/j0Meig9qL/yPN6MeL01kTi7GyW7vLFjh77Bah68UEVGvJWNAT6vf3o9QsMMPkr0KT6BBc29wSfpvSo3Oz6n6iu+RBFVPYuJPb2GeT895H0tvtKrMD5x69s9uT+WvfniWjwUSDm+t9RZvjQ4Bz7LpeC9KnkDvlnUAr5zuhi+isMcvpP9Uj3z7wq+","OwoWPv0ojbyo/li9jKavPN8RND7p2jY9OXcBvoXj1zhWnVu+TQ/KPVPlZ72kfMw95G5XveifZz1lY6W9Mll8OytdMj6nZx6+hnWHPLGS2bxMpAG+sLmsvYzZoLyr6is9Ku71PW/7cj3tnvq8gj5pO24M3j0Z7PE9dgKdPR2/ALhsQ+c9GQ2IPWrMIz3OwTO9WDkLPRNqhb1HTik9yDt+vW9fszz84iI+nFTRO9Lk6D32Dqy9lC15vSLfEj5oJNA9FVygvQqJ2b1BNQC8aXKnPe8duT0Uoh09/yoGPtQ3Wz23wV88JodBPvgC0j1m1v69JLkcPe2xvb3wK4s9VTP8PfEzFL1DTT49JRHdPSNYtzx6LQM7kwt0PZMyYT0Es7w9oeWEPTcKvD1fHCo9WIigPZHr9j3Uhb09+hJavWpMAD16xyE847IfPB3k670nHqS93yTqPekt/D16N3Y93Ns7vMswxbwkRkI+4CvfPapQuL1nZyK85T5APUpGJL5/H18+PTKmPFfDCj53sr89KUuBPNusDz20L8G9ByUSvsxXaj1x5KO9HlLsPd8ViL2nkRc+UpXtvPAJnj0ciAM96yepOwX2cj1RMzy8lx1oPrevzT0A1rQ9zDEGPrrmR74BxZa9K8TCPcvTzz0Cfk87+g4tvRN73b31Kbo+AP+3vJznED6NHqu9r7ciPtSU5z0MXAm7BR/BPWFMDz7IL1q+yeWFuwTvsDtlZmE9hEyNu1iRgz0mTxC9gR8wu45obz2Y08I9ET0IPrpeHj0S+Ik9vwUUPpnkqj2Gzo+9nYsovRbtDj1O4d+99eIdPYVCZDuBeMm9+tw4vK/zr72wRxY+8JZGPQ9X8bzB6lQ9K7atO1XUyzxocbm8vosAPRnqvr0jY9q7XXkHvnESob2/Ffo8tvmmvQC6pT0+SdQ8kUwOPhMTDL5IFty9xtluvSNvq72SrLO915sSvbbmyT1aKEO9XJjhvQwQTzxCryu+0AxzPKNn6j1GIby9MbEoPZ4QR7xIO4+9","8ZEdPk0vLD3IOsG9EgTtvN2cID1WOXS8WgsFPdfZk71hC7G9TaZ1vP9JqLwY+iw+aC7TPfUXfb4F0Oq9XeKkvYB6lzz5fNy9fFGhvJ2+172Z5yo+5niyvRr8xjxQyQA9w7+1vH+0CT2xRAy9IgjrPZ+udL2NLsg9cxG6vZQaD71xc2K+8eg6vE2wJj1LTOo9NLCGu1qquDzkqcS98q1wvIc9Jj7ZsP294QOmPdvghL6oX9M9asSlOrDLEL78a468l2cSvWATtj1e0p09Nw+zu76NnT24ks49TP5bvWn2WT2+4Pa7ssypPWPuQDsxArW9zvVsvWeynj0WZIG9cjXhu+VVnLwCkgK+F1a1PNnMuTzmkZk9IcGYPWsM3r1npyM+QmcWPRTZTz4op+68UxSwu6xGxrzeD5A9zHSbPMHT0jzVabQ9Dh4lPVLd0Lp6rR29TXigvYPgdzyDWHI9vCyWvfbDfj3KEQS98O5RvOurjL0ZCw89hw6rPJtIA74IOfM9KffSPZqwCr45n2G9o4EqPK2TI7x+09Y8DKt1PdO6nr35ZKk9Et0avtvxrr0B3AM9a8JqPLdyWD2fVWa+LoPcvepaYz5YHNk9RnPBvUnpHTxfgzs+HNtSvcA6K75oT3++s6SPPfBSwbtdDKe8e9nLPM64obyJxve92aavvbsHbzy/Iti9bh34vMlSLL0TCF27hl0WPA1tq71xnRm9Hn4CPlVq2D16Pxc9b0WBvQLroD1eRtK9Y75qPReYzLyR57S9ZS4BvH8jDL6NHg0+Z1YkvVPJzz2uYsI9qJDJPOt/Qb27lls9WQYzvfDQrz1aeXO9KNS1PaGrB72VuKa9oA7YPQ2ffzzZYNu9EwKkvbo6cT2AgKE9GBgAvhRO+rwITke8t7cqvi4CrT5IwI89H4a7PRbBHry3EGg9q+MMuyuZ1712dEI93pmMO6iDkbm6QZw96BpbPC/Gjz2Ylbi90kzNvUCsab3VnPS9ipqSPUi6QL2yJAo9LJb1PeyoCb2tRjS+","kToSPkYvHz7xJLM88DgyvkBpujlNZuG9QJv6PeK0Pb7Npcu8zxwhvnFYFb2cZhQ9d9SgPNudob0+ZoM8vWv2vW/De73v0ug8GLWDvePt0b0Divc9jEYlvvu14b0T68C9ITVDvRWaAj2XvdW9MwjLPZi557wF8+29N3dGPWYDlryxWoA9ToFQvYn7gT2VCU092u8BvqTfNT6Gby29qLyCPQbAYD50qMK90vVWvfWwqD0tETY9wYR9PQqCqT3F3LC9DKSmPfEHkr1TXRG9+MHwPdAJpT3nXC29XOp7vMcCj7uXVx++bPkEuynsxr3kSe29gq5SPBEBsTxN91c8oj6hPE04Pj6itHU852IIPq87VzuZaws+2AMcPu/vQjtX+6e8WL/+vPxUL7yz8wM+Ld8JPg0F+L2VdRQ+ORdevd0/o7wc6VQ88k+IPYiWzLuKUJW8/3UVvDyovb1MRHC9W4CPPfttcjt/+kI84kVPvS+pDD4Z/lc7cwYJPl+00Lzg3mi9MOMKPm8eibxNz9Q8/3NDvdRhJT3ScIM8f0PRvOAEvj24x+69Q5D9u5V/nDzuwb49gPHHPcNSNjy5O6c9ZdQMvZcVAT6HRNI9P/aLvTkr3jw4izA+QH8qvVhYpz3/RMI9RKiYvVIePz3Mcns8Bg6NPBWy1rzURdC8kcGhOwNLTT2lOpu8Bi4gPaaZ3jw9tTW+JeaZPf9bEr1peRM+PA93PYL6V70UFxC+WGHgPGgyzzwZcv+8GEFPvRr9Mb15zrU9KTLIPD5rlL22cKw9vgYovmELujzDPuS9rbF6va5+Oj2U3mg9AXuBPTXq5j3UUFE80jY8O7BmaDq2Mtu8vlQXPdSYgT0rmFO8z6aovR0KBz4lfiC+Zp5bPSHzUr3noVA9pqi9PCKTlj2c7lI9CTkIvcjc9LwhGVO+tsD9PeAUlr2/UY08LZNjvUjsGb0e9g69rCPDPqI5Lj6E/8k9Ej4QProuBT6WxCa+UOIKvUuT97z9VN88V+JRvWxfIz0exWi8","xmoAvWQFxbwHLwy8+Y8Du7CgFzyhiMC9vfoava8vPjwn0JY8nEZBvYbyAD7ZmzE8yniPPUHQh7sxzNS8CbYgvkd35z2EcPG85ILJvaHHST1bOyY94KYIvXmIsjtxSU+9lePcvVLseb1N36k8BycnPQkXmr1nOBA9DCXcOzgE4z1RseW84Wx7PbkMZD0hB8I9FVEsPqh5r72VOOa9FAxEvlEaN75RfQq+LthpPdv+rrs1li08m4XdvV8Tnr0YRnM8ZbxKPCHlfLw2ojg9WG2wPQ60fD2JXKi90ZlBvIm13b1NupM7VxBAPOf00zz5aTi9vC7svNer0j3CM9y83ljHukAKiL68Hza9McUjvnwmKL5fKpy7f5gbPr5rlT3iQRI+lM9bvZdFET3ceQq9JxuWvcosOj1KCbQ9wTACPp7XtDx398C998WQPTY/fjuilYU9JObMveIO+T3ddie9w/Ozu0/RWr3CCt+9GtRDvjFSjL1uo1Q8zNMPvmfOKT6uv709r0jmOzQPkzxucOg9EWxIPKhSRD3lzz69QMChvd47XjzcHlM9dZMfPHp/ZT0S4oM9vvBnPbVyJTvoHMC9UzWEvSgtID0K8ka+nj8pvb/SoL1N6yk+3vwYvcWLlr0nZQ49vjDnPRFayb1ctRg8igzIvTkPFL3natI8w3NrPGCKJb4fAyk+gXC5ul1rPr7WOUC9QJgVvOkaQL0IRZw97iLPvSgByDuKoos8fgzVPcbrGj5rn6s9qyImPDcGUr3H/vc9tsWjPIX+Xb3hSWG8+iU9vXiRqryyuJW8AzkBPrSGQb52YqW8WteAvMm4ur3wzpA8BNlQvYZsiz6OuQq+lWnLvBcjdb1Hpy09XRaQPdqvhT34miu+hERJvRf5E70uoDC77IXCuyxXDj5RYbo9LYYMPMWhtDykfVW+0fZiPRMTeb3/R1c9Q6C2Pe2zlzya6Tm8vwmJPYhLUr0QovE93XCuPZpBg7wAJPc7dMC0PZc1bT3B9Ay+Jq4tvZqWrD0fBN29","Fk8APjH4Tr6CzLO9t7aSPYZ5IzxLm4y90Opnvmyifzw3npY8/kT9u+scsTx/rJI8i/gGvueXobxAWRa+qjQePYVIND1UxgE+Jb/MvSETLb2FiA++ruYCPmEjiD00rK09mdsOvbeZib1dkMW8RS7RvJSrDL7rgOK97LqVPT35AD4/8Zw8tVzFPS1fCr4ebr49PX8DPR/Wq711w7+8NEa9PaMPDb6d6X2+HG3PPT2BbTxK/GG9GZAPPkUzrzz1Kz28Z0eGPjOgqjwzwuk8RbVIPC0Rl72vjHg9FMaiPfglbD2uTRO+TZjMvUncfb2vS26+3nEDvR8Zfz2uBQU+Qj9HvG5Frb2kMRy+GlkwvVEgTT0gcLC7Ey1Dvc20o73MJvO9QBCaPdEzhDu7a049Ep6cvQOndj3fOwq+fLr1PBsvS72TNi08V15MvZAj7L1AfzW7pXHivRJRhD2tsnQ9uPOMPRHsgz2moTO+T87/vBB0Pz4pDwO+MkEOvU/tYr09xfG926urvclnyr3qtGk9FqAkuxeV8T2yyg09ZpKCvvW8Dr7/dZ07UojfPe/0sL2bN6m8GnIcvlIdtb287fM7OTOCO79Hjj18Qr29PPYdPRTBLD0yncg8qwhzvSD41LzWm5493pHbPSv9Pr546Xm+99IUvSzRQT2+khK9Ce0XvrVkmr2+azy+CZrXvbk+Ib0TPnC8xPZ0PBV2MLxfQZc+abrHvZKRB76xQdo8Lw6nvDl5mL0rGJI8ge/+vGMb7bw8er29sbyjPJhpRb0Ikzi+8RsUvujSIb0fhHA8HoAwPtRmFz0N3oW9Pbv7PasTLT1I7SA9bBORPS6nYLzhhie9gm9avFpe5bzLXOE8+0L3O464Nr64gaS9k9DOPZngwzyCuBO+DUGyPWQvz70onkm+Zwfnu3A2Eb5bRqm8+aSTvEcS+Duo72A9gtEhPh/7lTuc/v+9shnbvX7xoz0xTJe9wQ/fPT1wEb0RbzI9uE66PXDlE75c5yO9uiOzvAuhqzvV624+","k92hPKrDJL12bSe9LhavvNWMpD1BFbk9q3h/Pd68XD2sJyw+R2g6uZE9hr1fEEM8hWNVPu+w5rxlzkQ9iN4ZvYDMOb1na/C7TEpsOl5ohb01eda93yL2PfBWCr5/nUy+FoesvX5xu7zVvK682oKZPfzvGz3dTqy8nSP9POUsejtm7jC+5S0aPbVfwz3TZQ89F9zFvGjOP733+uY8GKTDPGKbFL2gx8O9tk6svf9fjj18eO68rSf+PZjQ1b3XqF69hgclvp8F6Tz0FqC9UXMYvg+Z5jx4kVk8fz7uPJsZY719eqw8EMBNvC9odzpsVtK9iWZLvlmPNT67mFw+g8QUvhGF1L2y0Rc8sRg7vWidST3Tozq7lBBJPeX+gLxUxAG+On0UveKznb0ATQ8+DQQJvbB4njzFYL+7hI2BvDlcTb2J9SO+kfkXvspLQj2STbE8MlEsPjN+ED6pExA+i2u1PZvQgb3vHZs8oweNPcflFbrVOki+t2MYvX4c8z2dPYY87wn9vLNifL0CdZK9UFOTvCXdbD2bY8a9yr+NvSd1Xr34+vC97wgQPsjlFD5zAPq9I4fSve0h0D1JJFq9lApYPfzJgT3rM5I8yEoXPjFMCryM324+8ZlRPsJkDb298sK8iVrCvZIxz72QEKU9p4PFPWMgDr7h8Dm8LuuIPnSbXb4A9uO9ul5rPbgl5b3hFsO9uwFbPaHmtr3F/2o8J3d+vorc7b255Xq8q0+TPT60nj2QRb68BFd6PR+877yKCOc7SLTZPaHmUTxKYw895NaHPRp7WT6hSAq+OtgGvQva6z0PQmE8758BvprJ6rzg+Q0+3qMnvlTB8D2MHqE9isfTPQOALj4SljG+9/TpPZRoKb7gSKy8/OxiPsri9jtELA2+MrSQPeNRqj1lAuU8Pz7APbYKs72cXUW+YBi1vVBuzz0AE5W96TizvQDo5bxhs0C9jtARPWn4m76s9QA+t2HKPZxCv7oqQny9Wn21Pt7j0rxfzDA8wVeRvljNNr1zooo9","pOBbvQHonLywObQ909ubvVLGW72w2g89zOAYvuDQSj5i/Bi+9G6wPH2eSj2T5Ka9iMwuvXZMkj03/Qg+8uqPvW5CdTysbGK+40ETPa5egT0XOWO9OWRBPUmtFL6DOvM9aRRQPRwnur3NMbI9Nnnxvf7nyj2KyiA+UrIWPVgEXz2P7Eq7nOM/PRwg5bttC709QPHfO+hWN75MlUi8lCUSvVFngj1pjcm8nKWUPBmDYL0GnTw9iLjdPL1iorss00A971cAvFzInj1DxkW9yms/vSjjND12ob8922kWvYaBS7015au7/yyMPaexDT3b67e9hi/TPYZqLr5Y1gw8uCYuvZOOdD7uZty9OVLzPK+mjz0NZxC+zgTAvF4bKD25Wyk+NzLyPO14cD1sRi29BUw7vuWhQj4x7Ns88zLRPG45wzzAGya81z00Pq7eIj3N2808MtLFvf70E77T59k965iPPJ9JRbxdK229cNmSPVGHGz2nSZa9ogsJPXRNhLyQbqq99wjBPQt07zx9wWa9kKV5Pdituz1CGC07GkZNPPLRJj7NDQw+ApHWvTUlA76GV7G9+5JPvRrAEj6N2hg95p34PT3hJL46UNy9gPSjvFI2qL05MAi9GWeEvMXhwbxmWJW98p5mPeIeA77IcSW969CKPFu5A767Mle9qaaEPOSoZj2RmXi8A61MPdCCxrz/m529ScaHO8D+Jz183aG9Ceu8vSPuhL1JmQw+fzsvPeB2C7wjB/o8cj7JPFIDpD3Jah2+IKjCvIY+0L2DDDy9409rvnJF3D0oP+I9ec5RvWargrwJE3k81riEPU36fD2Ta+M8CISnOiTJwz2u/nw9n+s+vfkT9j2WrZy8rEmwPUnpED7832G+NP66u707Xz2UyqW9fCGgPOr8Tj3cgEK+QSscPgsc0L1n5Ty9+nKjPLnffT0dwtW8b/K+PBmeAD7YzGa9VdxQvJgjJz3XNoU9jmkNPotTWz06d5U7wYoWuxKNyTzFEIe9HP3dvHS7kDyi+pC+","LhDJPcmA3rtMXX496i+bPR77Kj0bNpW8EZ/QPU/xUj2OjE68jy/EvZEeGjtJlwA9j2i+PZjKZDzA6Vs80W2sPbZs7LvjRMS8IH0+Pv8J2rztai48znwvPZf7yj0ZLZ095woAPUGS0j0yc4I9m9MDvtK/GT71xFW9nKT+u/uCzTw9cl28Ot69PKdTD74yq1+9iQeHPb/45Lp8AzO9FBt/PpqmIz3RIf29JdDZPHv2rr2RU5U93ar/PUxD/jxBhxo8LTnyvaIYjb0Cvdo8C4J0vCKTvr0xfU67T7civu2HNb0bP3098q9hPECyg72LAJg9h/OoPAxXzby0pYw8Sd9kvYzpC77o/7O92FTPPGwdOT2tBOu8oiojPlzMLL1DWuq9ZFvLPCvqnzwZcbU9DydjPPixPT0sD9Q8lb1IvUaPoj2inFw9UGiAve/pAb1/8k493/2FvaBw4Dw+02S81+dRPWtWRLzlgFY90BUKPPj2rr17P7C982eSPWoGOD2Kdp29ETYxvHF0dbrlbJo9knfgPYGAbj4vekw9T+hvvRqd7jxEsIq9i7BPvCVhT72zIQA935SUvfxLPL1Scbc9DybVPN+n7j0nnWG9Jq+FO42RFb3o8Cy+ZZG8PXKOpTzfMJ69V0mLPC9XNruSr7u9msMovixonr2CxtG8I/FfPWhX/T02aks9PR/QPMfltL22yjg9e1BcPfy+MD4tr3k9XTNdvXVapz08Ij69dlaZvSW6N72lbq+8FJq8u9o5Br1WAYW7xqf3vUR0571CduW8PMqJPJISyL38ID+84q9ePVNgNT4AFfQ8u9bTvRyWYz1kuVC96acmvc3iLr3p9s88ZOiPPQcAMD0Xdai7QVfrPHu/uT09C888uUvuvLVgl7wYRBO9M20FPQtgLz40Mi68ltgyPC6CgD2QBye9Xb42PKg1Vz2zKjO9OnwPOxVNML0kGAe+ljonvr/PMz3u25i9c+cdvvaLCb3elzi9r0/ivUk9Vb0FJVY9T/ZjPB//qb0yELk9","iIr0vcMYdD2XXwS+ncPHPPNMFT7vbPY7uizfvdsfAD5oj/w9C5eIPZq9iryGVJ29uQJnPYackT1CfFk9+rJ9vpm25z0+VNS9V3qGPg+ej71Mkc69XZ9Lvv9U5Tz4+cs8NFzEPJdwMz6aVao9b+m3vOmV5LvjB/c9QGDDPeHK/T29xzC+TduWvGinaj00l+E8j8suuyIvi74faFK98v7yPSqNFD2yR48+7ebsPMajR74RYx0+o7RCPZgB5j0vz9O8r8hjPgvUkz3yiYG9n5K0vfUFwD0yFw+9xQaYvWOrg701Xo+9Z+86vmzaOL4bwpI+jp6RPa6cULxrlg49bVHbPWoyAT4rcHO988UhvS8sbr0I7wG9OZAJviYbg72fkp+9zkJfPaExmT13b0+94onDuiXq+L09Lbo9s55BvgGlgz3mKdE9dJv8vICx/bzyQ7G8r2Y8vjMWtL0uuPI7EYELPqpjZL3WH+K9qawuPrTPIL2Y2Ru+CRBKvAQtYTyvpmC98sacOzrpjDt547E9ekXoPfc0iD33leu9WQAHPso9Wb0K6Ks7TYYnPvgMYj6bAIA9fOAGvR1GdLuysgq+t7MYPeo/sz08CbY9OmMyPmJq0zoQZks9etpFPsNsszzG0T89qjcdPp44Kr6vPq0+doI6Pdazkj3gcCG+03g3PdpnPDzQMJO9967aOvRgzz0BkwA+mvwzvAndwL1MDMQ8Vy4FP2ZsJ763+DG9xgkxvkERDb5gZRc+bQ4NPrEvCT22OuS8RcYaPkpd+ToYryO8X/NRvnh2ObnlywW+7NDoPd0ErT1sRUy+zF4Kvvy93bwP9aM9Q60FPghgCr0t3aI99V8qPS4eCrtWSz89Xy1Uu87yA71IOum9nsERvaM74L0cwNM892u3PsL7srz9Zyg+gNKBPLInlz01RJo8u4/kPcTytbkQdxO78rOEvmIN+73ZeeE9ChJUPbf2y73LQ4G8jK+RPnVbVjtgP4w8jo8TPZ1tD755LMU99Aj4vYIcOrwNLuy9","kI4qPoT/Ur0JOde8ar/VvaSyybzYKGO9xLBOvBDtFD6GOh88z20QPUxxFz4Wszi8Yb7RvQh1fz7YNuW8qi+Ovs3/XT0/FmI+mbQZvREb7b3ARls8P592vYTVB74Nb+S9VfWWvfNDUzzZ1Ho8jb1PvscpRT6h6wM9RR4RvSMdcT3EqnY9QS+pPZO1qTyWaPk9GXZFPgB2B71Gk6s8pkXHvVp1KzwqsZk9EVJcvQzaHj7QvMu9H2YXPReKIT7SkFs9vE/CPdaqx70aJTO+SDElvnCH2r1n9KA9wGm3PeAN7L3kbBc+UHW1PSNGGj3TgZg9/s7yvZ0ZBL7hssU8eC65Pd0+6Lyxenm9RZGrvfdE67zGOTK+ytKovKd4U745iQA+WRCxPD4rzD1vIE09PneKurgwjz27+rm9TSd9vaooHb73c2E9b2odPbfx9TzBKog9N4qAvPDHpb2Xf/I9R4KbPf+Lz72T2pg81XCcPdtr/LxdUsI9vA+oPW6cEr1RkWe8S34jvtzcKL3wfwU99CuuvDPMNT6Jqra9SdczvU/xIj0eE6k6vaEqPbBFCTw7Mew9uWU4vJ/AHrzpnh69zQurO4GpRb1GucE8keNPvt3yqr0FmeU9ziMqPQs+xr04T3299TmKvSYIeD0ZaaA9BtA5PVtxDD6pj1e9UfdfvSParb6Ywne9nL9rPWKie71PoQq+hY8+O6vsTL2H2hU9028evkwXfzxO9x4+02HMvCsHXz1X0rO9G+CCPf4JIz0xF+c91nqUPS4NMb78ggc+IlnlPQqFsDyi06w9V52VPVKf273kGmu9TYFevQaF4j1dSnS9VGS3vW9NvLyFeEy9jCYNvs3/gb0zgjs+0QyOOlva9r1/z16+GW2wvZ4MUD1p1dI97l9rPJVR3zxs/Fm9VXnvvOBttbzRHIK9u66pPCT64L3Pu4+8M0cOvqZ89z2hRjk9ZY1fvei1Wz5p7m69/Cx9vCyi1rxAVzg9HC44PSWetL0vfJi9UBmbvfOzdz1jFw8+","OvUWPgvVqDyJZMg9bjRdPv01sj3YkrY9Vkf0PXqIjr1nVZu8fOm9vTHDh72XGZE8sdKhvUA+F7uEJS29GxxQvlEkTr2Kqhe+T5QOvtcSIL12n3e9IJmgvUG+Xb0wOSy863LVuyt9mb0qqUs8xkDVvfZnXT2DaPE9QNNBvqytIj2ceRe9+GUZO0cQxz3YXZi9rbImvjA6ED5f+He99awovRn2B7xGB0y8p+u0Pc4r473zxn49KSH6u/wzKT2dLzo93wysvcVdzj2ZRZY89gfCPTJIgb3r0jq9kIYWumD7Lr6Tpws+XWwNvYkipj2Uh269udotvlFPoz1mjGO7aGATvZt7nb2ysf+9G7AzvubQvLxTRqQ9gx4nPsLexzzTN8m8Jh0rvAkKdb2rkP294jUAPiBMgL1zMEG7kQ1AvBsTwL2rlqw7UfmmvT8DdL010FM9BYYcvihxpj3Ez7k95aT2vBGiM74tZki95ayUvFpH2D0bYni98FwavMqWr70jCAG+BV8IvgMZVjy0fj092P/LPbkb0T2QX4w8CD6pvf6hkTvnLdc90h8fPfqxmLwMiA0+EJkBvYHivLynfZ89ikMuvfu6dL2DoQg9wIVzvhiIRD1cyco9IIv5vEWO8DsUgMu9oA+vvE3/mj1Aals9/Yavve/HYD3Qtp49wdjVPbTIzD2QFT89iy/tPRQL9TzOi2q9L4rnvXHzqz0YHYG8HWCYOuA7Uj13P9U9+JK6PTWVlrwa/Q68zgXEvbHeYD1Vm749Gt9Tvbbmqz2zHas88coXvZjsHj5lkcc9xUGxvagekzwlt4i8Ze6Ivd3flz1+m2I9tiqsvYYt4DpKU249vGRRvXr+Bz1Y6Tg9o0owPMBpHL0y1Bc9yQHzvEPSMD0Qo4C9LOWcvN2/Q70NKsq9oA+APf9URT1Rbui9Cy4Svae11zuo4SQ9PdD1vSM9WD0Yins9CH2DPSa8xD0p/6O9PhEDPmemmLxifVk+cF25vZ9D3D0kGGu9Ln7UPHLNtT2voG0+","dLQcvnyGjj3+dtU9s/9lvdnf+7lWSDg9zg2EvQrzE70QB4o9lsnfPVvFGTxDzmm+qFHBPEt5p72ExRM+kuxpvS+zgD3gVB2+8y3fvTOyqzyJQm69dk1NvU/Or72O3hU9vdi4PMYYrj1haoY9jxhtPaIZzL30Z7s8RsjQvaPAZz02d/697u2vvav1+jwIA+i947AKPvKL2TwL3Iu8XvtcvTeIDz0u0sO9PY12vtTpzDxqcY092gJKPQRKdD6l0hQ+euS1PE92Tz0ZvR6+FuH2PWwMFz1J7YG9uQ6qvbLM3r3k3LY9XpRVPf+M0r3ewT+9TELTPaNXAb3wl3K+LdlYvfceN77++rQ9e8zFPcJR6DuFC6e9Nk/nPZAm3j17IwM++QpMPnFwsr17MuW9eDoNPpMEezuya6e91vsZva2MoL3QL7c99dOwvczShb2ZiB89D0QiPqH5wj3+suK9eF72PWrtEj5G5SG6HpLqPLjLqrxvTrM8tibqPffpBr7rPbg9eStpvcQLFT3PArE8pyx0PQe4v70n71M9nnbyvECH4Ds/gX0+rW16vYzxBz750v49aiDpvIC31702sIy9CBlnvOx1ub1RyQK9RLVyvZZzkz3qss48+Qn8PJpBwr2L9ws+6jeDPPNNML1Z/n08Sj6bPb02uT3Zwsu8ibYWPNvu0T1sKwU+4/phvQcubj0OeI48EKQZPR49gj3Cp8o9XzeuPXFAAbwGNaU9ImApvO2PKz5+qbk9LjIVPhHYCLzDXos8fVShPNNfWr1rMdI9rpfRvMMh8z1jmre9zj4Pvb9+zzx6bvU8tIGWPL/nQr1Y4FY9oLEfPcVyrz2Vk2g8GIGTvT8oJ74tBnK9at6avW6QWb1m+fe9bX4pvUeW3T2xrAC9defMvSH+ET5WqdK8EOPrPTV6+ryVk008KIRnvaNPDr1scJA9tBv7vIB85jxV7FC9O/WLuuqOhr0+gTU+2f8XPW3vbz3YcXq9IdFavNq0nL1ztNk8JZkBvBR9Vb6tiRw+","tecvvbBtW706vrA9vXL8vSEYCL3QCuG97beWvpE7OT0PkdC8bCFivNQORL0gcpA9Lh+FPY/xJj3yisC9hhKQPLCoT71n78G99DWqPRB7/L3LSvE8V/COvNoYbT3xbmg9DfdIO0M48b3EtLy7eM7cva/xnz0MHQO9qLwHvXZtcjtmL269dOWivV8VaT2NIdK9gN0HPmVAlr3BO6w8ypQ4vgJrwj2Ioq89eKAGu9+cyjznuna9I9z4PFwVob0+A5U90KJpPnfCOjtfABK+BbV2vo8p4j30g7o91dqUPf0Ci72uWm69Nm8CPgWoVz3JOYO+ZTb2vHMa4L05Cja+LzpavvebWD1w+Ae+AMkBuvxN9L20IoY8BUg/vRFEE77Merc8naUlvfYXnT3kJIy9bpaxvENtk72VBRC+kNuVPf7i9j2iBYi9qW0ZPp2Mij1L4OQ9A5pXvfIOGD5AVZO8CYDhvQdsNr5oHNu95lk3PeZnAb52t509agtcvXbIqj0rtWS+tVLYPQiNyTyW7Re87vAXviie2z2FThs9mrSIviit0r2rFYm8YNBfPgWhfryJ/wW+68YxPAMRar1rvY09VQ9oPXQCib0I3Ak9aJqcPZaFyrwzv6M8KVewvf0Chj1U10G9vbmmvUICHT41SGO9wQHdPK5kUT0n84y9M9i7PBFwOr4njD++7MPSPSTaQb5ib4k80jzPPOpqE77BZEY+XHLxPcxIKz3Mks29vDB+PcycyrxpXCC9tJ4cvkD8sL1X1Ky8UztmvRVycj1vDD69nxCsvP+odL1Y4sO8nazSPZ31C72mxag8KXnIvVWvjz2maD88E3XiPQcgEL0Xi4M9xugsPJ7r2jyTsn49RyD+vQqzXb4JU1S+vKy1PZYudD1GnoI9GMWSPng9Zz0jGYW7kFiqPf/Jh73U7dq6SmoiPRVD9Tz0h6s9NgWKvUZCwbzg4929NfW1PUNd/L2Fi2696DEdPvYF2r0far687yP8OssNOj3qfQW9vZtKPT1KUb03Fos9","4OUHva/RHr5I+um80kElvTZsID2JB7k9Ztf1PG5lDj734tK9UrNxu+7ghD2E+bY75Oy7vYLRaj1hOkQ9PDmJvO3TAT7VKXW+bQYEPkQVnD2GVhy9flcfvaS58r3lS+e9pYPKvFYONjwPkxi8/f5KPX28G77Mrz49DjUXvk8P2rwNWYq96dIBu6FKsD2+i1y9raYBvh4MhDzJXCe9+pHKvICYBr2UmXY9KEmJPfHjmz1ypao966yCPN/P3j1kooi9tPZOPWGQHT6F2hG8XySyvUCEVD11L8M704NiPctojT04o729x2nJPZzDvT2kbT+8M0wlvljsPj01NEC+2cgSPegwvjwWO9S8yDDRO5Wqa763WJ49JpfLvWQ7Sz7ZX4C9+OJivR9ZTD2+gYi85QRevSskFj6OlQe9ysVePUY69r3Jgu49/wzuOzVDED6WEYO9oVCEPalvlj2do209YUT1u9jyBz7xdw8+RBDjPAeGj719LXe9l/KsPbKdnbz1vAu+ZHE6PvEKUDsoBOU9CC98vGJ9gD1zuDK9oJlAvmd1yT23ZX29MAJSPnyV6j169P69tEdJPRiw0r1djb2955eUvb8Zo77bBx09vRhTPfKI4jsOvi48f4eQvXxCGr7ER108KTtvPNRJMz3BBF69SyHYPZ9Gz70Ly868qnKcvRSdOT0cSzE+8oFqPdo6Er4bUn+9e6pnPE+lT72+n1G9htOSvR3qcD219xq9DGbrPDGDPb1Uvom9iFXHvFUVqzyA5bG95dbcO04pID6LzOo9gMLcO5JP+z1CkmW91kHavdKthr2iQrM9Pz+kvFd5071V6yW+G9xLvuK3Mb71qck9OFqQvfEh4b3BCLM9p/jSvdDQ1j0WPL+6ey+tPSGoib2Tov48pYofvb886T216TI9Pg+Xvagmy7pqz4O9s7xYPvMBfr3zHiy9768dvmiBXTq4qBo+5u2ovGb0xj21IoQ7KuPDPUObcD0KsQI+BLjuPXuXjLxQKA6+E+OKPu2wQj6SkoQ9","jFSDPfytfz0SIYu9WRBpvJd0rz10yZ69PyCrvchYqb2zoDI+SQnKvUaOnj331Cu8jEXxO+MlYz1uw5G9o/qfvfhlV72YuME9YZgEPnnL1Dxrjg8+YkOBvUBsObzMPhe95SzNPOnwFr2OUn68MKk5vWmtp70iijk8ye0EPRMV2bqtZpy9i0YEveBdUb4dXX69HkUhPegsPruIcEQ9WLCPvUT2wL0WVNA8T5wfPQMUlrxME3G9DO2APBaTjT05s/u9JYGAvW82xrznL5e8R/0oPG5QoTxC08c9dI87PRxwEz7pP489lWHBPZ9RV7x75HK9up5gvcL8Br2S9og9/m8QvsrDRTySOL09BXbnPVtjTLyX3yy+eMZDPQK3Kj6oIIO84XEuvU4YSL1EagO+PaE5vKJrhb2elGa9/joLvYj1xD23HDu+3RYKvYU1hbw4+Vk8+b0YPhrQljwYIn+9/oNRvtYpI72FV+W8ZjxLPtOpB74FawA9tQBXvfSuSL2dmBw8/Ausuww4AT1x8NY9maURPpWITT3YxaG8pQ1PvWp4pjxZv0c9YiWkPGo0mT1S35u7u0LBvEWeNb7LReu9WjEGPQvooT2S93G+CYY7PR5LsL1zmB6+pgY2vWtdVL3hO6k977sXPUppQT0OAr881AqaPcMHIz7wPaO9Glb+PXREHD2Dopu91sy9PYOChT3kb5g9jWqhPbd54L0EwsC9dxRNPdXsOD2ozes8NfSpvGub9byJEtK9/PimPVe39rwmFXC9yxsDPTbH3bpUlW+9l0IGPnb7gz0LJxS9yY4RvrwvvL2BrY69MtoBvhKcjr2+LHk8XxS9vJGltL2w/Kq98QXEu6ZKIT1GTmG9hJUwPQovI77uDyA+9neuPUCX3T0j0NC8MhuxvXYdHbzg5AS+LM6Tvq4CWTxz9Bu9CJ+SPT21jj0uCey6eZKrPfOfej1BquE9QyRGvlDLyT2LSBC+id1svSA7FLxyg8m9D7xCvZX88D2iOTC96YNGPddbSD1vWzC9","Grm3vcODJD268HI94YbXPZPiS733/ys8hm4+vIIoRrxYgcg8IqsrvhW1S71uZEs9JohUPYB1MzykYY897gsovmzEI75vdua9xG+mPSnGqD2d6eO9rsxvPdbSKL6QX5Y9fWBjO2CT6bwZYO29pZ9ZPHN23zyoN0o+3lkuPf2r/z2oSwQ9b2v/Pb+bVr1GQRW+UExhPfSwfL3cAIA9WiUNvRBRx7vU/j++83CKPUPfALvboiC+7kZsvUqGHj1RaIw9gW0lvBd3gj0cIXy+cZV7vBY8rz0oAUi9QQKaPWU+xL3+f9u9Mt5VviA/lj4bVW09PvYTvfzQub2bjB8+UWwfPvk+Oj79+iK+wTKAumChyD3GkTO9XFC9vZXQCT4OiAK+PJAvPbtOEj3dq5i9O/zQvf6O9r2flNw9fKCFvdQ/HD2Umkq9QJNmvpDDMr7Y9yu9ON7FPZX+xL1GrHi9UgOivDQVEj0btFM7yk8QvbvPUT6UyAy+12v2vPmn670hO2s9l3IdPW0T0j0mg9Q9vhbKPYz4dL7cirw9lqOqvageJb394O49yO8yPWnwVjv1vpM9NIX1vSm1TL0Uy9q9KC+ZvbOLOL5POM69uTmqvcamkz0Xr1G6E6GBPCWsAr6Cfr69asLBvTEcxz0mpBg9zDX1PPA2cb2uD5s9YDlVvSc4wz16MR8+x5qVvUXz7rzm0YK9fzwgvbcKRb73p4y9i7KKPUuSDjwfg+U89RnovdlbDj1q/Li8xnznvAqMoTrJQRY+hlsCvj3qnzu9CIY91qbdvToM9zxRvhc8AwdsPorijb39zyC9WcUpvUT18TsTAt+9jPeAvXJwHL48ZG+9pg7rvZvHp704ctK8N87iPEkulT3IGpK9aEeaPFAPsz0HpL47Yn3JvS6imr25leq8xMRjPtCNMr6Y50O9xkQuPaHqaT278wQ8PJjkPO5+bDw3f1k9/8NBvuoP5L0Cfgm+5/5hPYdahL07JXy9ds9oPWkohD29J4U905bpvRyTBr6OeK69","Yq5KPjkR1z17mbE99Dr8PXf3+T1854y9lN0svjf9K74+97O9YuOZPNp7Yr3cV0Q9U+2NvWecjLxHEiq++qSKPBkqB759E3E+3rtcPMsugr0OGRQ7siK5PfvlQj2GtkK8G7pjvDJVF711WMG9mN+RvZ2/XL5Mh0M+Iz5KvnjgjT5ZA8A9yOYCPQpbE70uJ5Y6F+q7PPoZwD2J2vq9xQYevWb3Azzj6sQ+qBvEvPLen73l/me9WoD2PXkBVL6bqGI9lBvsPEpdC770RdY8lcmZvFRigjw1wZ09Cqg7PV118jwGLg4+woQcPYO5Az2vkGM+FIBMPVPgljzteww+8PiSPMylIb42N5O9B+O/PVEeYb0K4bM8CXqdPXx8lb2A6pS9SHYQPY2MMzxq1pq8QjyjPbTKlb2nM5O95eW/vdOS6z1Ru+a91y24PdmvpTyAGeC8C1iEveLSXbwhWF4+z3Envkv6ED4+G+I9PPWcPVKILL5q+4+9rEifPJh+GL3DgXM8Wtg0vcwP0z0GIJO+7+MUvjAJQT2TUlG9A1Y2vttsBT0xT8u9m7x7Pmtx2D1hBCy9oMBavKREAD2hZLM9PtVKPbz31DwE5hq+b320vWMcuT2+oSG+ld6avWR1qL2/9cg81i9ovRwvnbzuMjU66u+0PdKoYj0WzNi92gfHPnVfQb7GCKu9/BuCvVJ33bt8dTc+hqxrPQLRYD3B1dU9QCXqPhIaIz7JVRS+RklePCqRML6BPVO+ert7PQyAArxPXaM9of0KPSIzlD3q8b47sXNMPITqBz7zgy2+Ffh/vWybf70jsQW9HDreO9z4ijzfew4+Ia51PetdOLnSIeC9LkFpPXDByztq5zw9CwQxvcg7uLyyatw9RhLyvfR0NDyPiqW9w5SNvCBKQj1V8au9ZPqJPcMbBTyZZTO9y795vUmpmb2mSqO9//gZvuzGWT35OP07gDQKPtP4eD0njzs9a/xSvoCewr1lmhc+nyDLumhen72ArSs9kfkxPfP0s7uX2Cu8","UuxsPWk62z3U6ek9E01lvPXIpL5I/mW9RdvMvYUK3z2A/cw9LcwFvFmMAD6Aihk9pEjvPQFm7zwq+qM9YqkBPp7npb0f+hG+kXzbPAGuFT5ohEU+pTMnPCE/Sb6dy2Y9lf4oOyFKQT2YMMI913XkO8GWlr3Szgi8C1yCPTN4Nr6V7yQ8NKYhPRbx1zzHV/C9mcVBvUmqAr0SyWS9pHAfPLrISb1+wAS+sP4mPANey73JkiW9scCcvRSCvTt0j+O8KEjGvPw/ETv1gq49s3P+vMcRx73PGfW9ZZQAPhmd4T2f3pg89qrXvW8WADwOfY09Gr0JPpF35jwf3+y9ilWVvTuDUj3GwPo8XPrwPCDcDr2H30A+Rjb9O1CJH76l/eC8RLIKPRXZzLzzUku9gUznvF371D1ewmy6GpRaPaS/C70vtbo9nr2hvY6qLTs3ADi9+dv7uxqTwbxrtxi9T7ORPaVirr1y0w2+aamFPcOzpbyV1gW8srDQvfg0GL4AaGo927z8vTtCmryD5aW95p0/PGBZXTzoyNq9+FjPvO2YAb4t+YI9CgeuPU4ZD75Y0wq+hVldPU2OML6xyVk9u4lWvTel/L3oqLi9Vc2mvCJynz3PpjQ9ySJQPk+GA77pTHO9GVQEvrMIjz0Tdxq9+aMavsV38L2odIu9T34MPjLX271XOAu+e7nWPTGkwz1+ucK8xTNiPOdfA74QuiK8uFrnPH2fcTqWtJe9Jop3PbqUWT052Jw7hiYqPbmy6Dwgbf89YNfROxMI57300gG9TztyvTW5yL0gLc69WdVaPoFLiDyE/sI8/cPyuxBaBr6I8gq+lxqePOAqOL26KlE9VocTPij18b29zcS82McFvr3x0zw/TqO7wBiOPZcwhz6T9SO+azp6PdXOK74QIM49fywQvo+WVryJPg49xT0dvrhTYD1VtwU+gTqfPczyUTzww6y9ZsRdvHhxrz1DGLo9RLHFvYfk1L7DysQ9uiN8vpLuGbwRQj0+lr5QPQQLiz471i4+","oTtsvSBUO777Mns95B9bPTbh1T1n7kE+ImV+vqy7Sj3anx49QhG+vJC6Tr0kAxe+qV3IvQscJz2opAC8mSGYvS/7DL1DRY09y2NGvSWPBz1alZ48l84ovZhP972PPcA9jCCBuoS4bj0wLLC9xQSLvRlbcr1Ik7q92fZfvQk6qztnq2M9QsUEPRklx7zhU3u9caPjvNoBJj1FOp298VeGPdZb+Lyc5/a8CH+CPc7pSTw+Vc074HCKvBDo7z1tL8Q7A+8DPTg7ir3jQCu9WNZfPUlX4z1laK89G1ibvflppD3ShY07z21xvGjJG7x9Cos9Q26JPdwpQT7MPHu9lJ5jvZjBP74KZM6922tsvphjgj22guO8NsdsPmXqMT4Vwzw9CQmCPfR5C72VZIc6mtefvmlwFj60FKA96IXxvXpF1j2UmP48OOmBPdZ9wz3CxiG82JlAPGjjSjyVQOO9/c7EvYjh+r1MkOq8Sb2xO8G4O75Aio09T379OnOchz3Wnc684farPADBO72O9zy6a3tDvbHHHz1bO229b6ADvv8MUTwGBG++/7NtvL4ubL3hcIo9Xy5vvU1NjL7jhE28em44vWE+CL6FYwi77dQyvlMd7zzN1iA+JtNQvZL7ujvr4Ym6vWm+PJ5JDD36iKm8+bPIPWtdTb0KIWi+LlwHPJs72j2+/AO+ziN1uiq2Cb166wE+AuJ0vWuHiT360jS9GsPfOzrN570f87Y9t0uHvSDPBL67ppc8UyERO0HpJj2gUSO9kK1RPeUN0D1o9Sm+YW+OvfhQqL2lZNG9hrBbPeE6A72dahe9Ku87PGsfvDyHoze9UGy4vY1iuL3yK5S85tAaPfhD2D0r+5A9cC2Kve6Q2b3ekbG9gwKGvSlY3b0Bvvc8wdO1vZRROz28l5W9iB+MvFJGEz3Ok2C991YJPZaYvL2dDS09L2CrvHb9nD1UFve9V441vkcbqDuuUdM8kWkFvcxkGjzFPb89uDrePC3puLwZsUU7hpucuxeDN7xviIK+","Asgmvcjugrw/lPa8gwuEvfjug7rTSaE81EnwPd6S8T2BXbG88q+LPS3EiL3P64A93i2Uvc/Qoz21Ltq9qU32Pchhfb1pqca94VgavmMGRj17sVg+Kdu5vQzj4r0qr7u8Rd0bvvuSwb3ZFjO8LQO0vYgtp726Sqy83XvdPfwGsb14qMy9xDspPDLRvLxIPg6+05PQvYA3jL2+y/Q9ZxXvvRgu4LtlTiw9J03qvV3Vsr3DA2g9estFvbjRJr0DasW9/HicvaAGz71H9LI97y3KPVqxFb6VQmM9QCDQvErgQT0dAMo8lnM9PNHukT1vmXK8fWtuPQwtX75eSYw+bu0zvgxoAz40LM28tWJIOn6LlT1KKIa9FfM5Ps1jaT3JsNA9x8tTPfz91Dt24gq+/7ACvdXF2jt82qu9wWeivOcF/zwPvlg9PTrAu3EN47w4nLg96yuSvGtI5b0w16I90SbxPHmMnb2NQQa9OuiHPP5lHj6+aJG9/RkJPdIXU77pXHQ9lcOFPWXphb2O/Ce+SV+ePZDpCD6Op+s8oL8EPfE9Xz0Cq2C+8SkzPdVZTT1wYu098lJuPa9A171cA1w9LcVFPVi3lL06UAO8DgliPehHkzz30na+sO4lvb0IiztXDRC+pdYfvuZLcb3NYs47TtdquTYoETyAPMc9200qvGAUGT4xcQu+cd9autI31b0U+aG9Bal/vCfQI76bv6O9OYlavRCusj0misK8/LEsvU10HTw8kp09ctGlPBwvZD2AzF++x0Uvvbmt+T2s3Yc8qJG+PZhd1b2CNUK8Cg1RvaGmx73wyqK7sxCWvUS9r73FiU8731MRPXcAVL6SJtS7YFqVPUrjnL3e1ZI86XHMvRzB8rwtFQM+9KHZvcC1Ur0dL969Z8CTPI4Zpz3WSL+9K+osvcQDLb7Sdm69NcBYvTRTg72YoJO9LWPXPIYGqjv4nx89pg1RvXxT6rxj+1W9YKPWvAIvrb0xbJA9LXCCvUwrED6gN+g8mykFvl24uj2yrFi9","P8ubvZW4Cr4M+1K9mIkXPZF9ir5ubjW9Isspvl3fcr3FtFu++y2ivQ18gL2Mr3k9lmHqvVkzDrlQUnu8F30RvvIWnL2cjR69i0I7Pd22Jr4j9F29bqs/vdemxz3wtwi+TaYOPZjDB73vDZO9qQABPLevzb1ds5C9ubUMvpBS7b3kabY8U4lTuve8rzyR8Jq9o3mhPcZbJT3w9wO+48fWvY6wVT1nFSk+0MztvVuxHL5duR0+r6IRPpWvzT1+X629LE25PZ4P6r2Q8Oa90JDAvdY1Yj1f7Mo9XYUFvr3q3D01V489AGvyvWgItz05c7a6eWn1PdwJWj20DxG+7h0WvjN9Jz3Gi948mbndvToStT2GPUS9sO/OvL8+Zjxp1ks9ARF6vcA9lz1YB7K6sYijPKMCV71D6Cm9Yml/PDKVLj7EeSY9H0LKvSmLKj3NRIA9+oeIvLEz/b1tGL09KXwKvmm8bD014Bo+Ra6dPVEw/z0eriW9ArIXvtZD6jyOQM29O6dTvZP9Tr5YTE+9q3XPPdonAb1+2Be++w5TPXrYg70CzBi9dyZkPlMfpL1L4LC9QVSyPSQhp73LofM9f6Bpu/2/NjyJCpq9hiEKPiPKSDwiw309MCcYPsuQrD2cWl+9ZJygvK0mzD0A5JM+PwbHOwaEtb2d+Vq9QcfcvppIkD162ge+PZR9PsQtBj4lP7W9qH2SvfJsFz2pxaO9IDfkPRvoa73xDUq9sD2Gvc5iwz0iywa9sxUVPTuxVb0UqS49gpDkPVhUMT6zjLY9ny02u/OisbyXAMs8a/uNvYP2aL7vxYG99HE8vDR0J7x5OCy9wZKyvbiIDr2RVFO78B6bvQtWbz0NMYm9mLT0PIUunL3ayD49kXXivbS6Rj0tSnI8RCCpvYVoCD0jcxI9lIpcvft7pr05N4S9ABn9PSRTjj1KY9Q8eoznvT+OmT0lXYC7gH8fvCtjV713wR0+QBdivtLhQj2CYN664wBAPEYljD2+rAo9Ked9vT/jSbvzFG49","prYwvHtbr71kA4A9NN6jvYTFwTs2zzK9PtOLvdlP8rvvMtA8erRwPdgOpr3ukMK9hkIFvgZmEr7FPP09VY2WPUfcOT1Rbkw+rvg3vQDhDj4P82u+aLpevfsSsr0KgoS9N+8Rvb7zgz0ywqO9kGKevER8UD5h/vS91IwVvh5ETDykDCk9uCh6PK3xQj18Dmq+Q9ZKPkhY/Dy1E8E8iXpHPCRJvD1ol987IMxcPVhtMz71mm89tRHavUp52T3diDa99Fy6vQ41Yb73NtW7v/b+PF6mRL4p3tm8AUXDvAtyEj7BcvY8X0FZPeZdbrxQe1+9YtXoPYI22j2X6Ki9X6lHvlyUlT3spLg8EkshvVRpq7wmwxI+JsLPPY/Fyb1f/Rg9GcjKvYnWVT5tlaA9Y8lvPJFCPj6zGxq9vn1SvXoVGL3nFk49gm8FPKEC5zzfmuq7UG+1PVeIyr2SnOE88NQtPdtv0z1i5C69cb2qvBpONrzd1Jy8Gt9VPV8rRz28wrG9yEsxvfxmzzxVGVS9WcVlvZVR5L0Jmay9up0OvAizZD1YY7u8EL5UvZLXoT01yz2+kuwqvZazUD1+PEc9XZmMvMyAoL3gBck9U4mJvFEbR7xcGvK92R58PuZbqz2pHv+8d1XuPfHROTtfEWg9gHBfPRKXY702oLm9gMt6vQtLpb3YYze8MlICPSBb5TxHXpc98b0kPdn8Vz2YTVu9yYeVPUpiVb0Xcxq8oxXJvSMC6z0vM7M83GFtPRkBeD243yO+GnyBPDxerL2jUjK8972zPU6WlT0Q8g68nkpePYUJX7wjSDG8lA81vmj90r2NjkQ9dQmFPZG0Rb03WBi85ZGFvVAOFr2UxLA9LRb4vOqSgL3t9r89c2k6vGXsJD3zpoO+9RIIPEgZT7wX+aw9FwBOPV6gS7wdvHw97DuvPdOlsjwBUEa9xGaXvcMtKb7Vsxa89LpPO7O7ZL3LOZ48+tltvYnTGr6/VIA8PaYOvmnMKD2hf0q92K3tvdrZrL0i9S29","MOCPPW2UIjvUY2C9d1kDPnoBUL0x/MW80OQ6PlVSpr1uSAA+V1DnPfAZgL1rqx88RrGNvkkPZ712Z9e8uaZcvEo9v7yRGA+++lEDvkPxCb7vers97YsEvgOvNDzWMvy7YZ2SPd98rD2dEoO9tTcnOgYRyLwL9CO981HnvfuJNLyW6Vq8E6IAvRdiaD2gsgm8zTnAPZXIEr0iMVo86NeWPZldRDwTja68WABBvaXxzbuOUpu8SgGOPQSqCr5p4Ki9q1ToPBEdLz4DKSQ9vqqIPOsMJb2Z0rc82R37vUjpujxzYRM9GQ9ZPUnH8TyR24o9ke8PvS81Dz3qHOa6Ar0APtqnBL4huAa9kZFMvHEyQj37hEK+xL8CvrI4nTukZm29YRCWPH4KCr1ksKy8sKDvvQ1hn76B1aa8gUDCvd8l+rlw/KI92gTmvFbafrvhMkE9cTAJPacS9708Qq49omIvvlPbizyMhNG8PKYnO36qrr0HyB+9JFqHu4tUILyEQuG9skLGPftp3DyE1Qy+MogHvreSc72YP4S8zNoxPcYYsjywwGI8603EvLxVuz3CuUk+Yq96veRtZz0Oa886uzWUvM9WNr6HY+e9vDjGvH7y6718Ko09ifBJO817Bb7QvQO+2Vr1vA20Kb2swzg9vOYFvF96gT3u5a29tHQ1PKFnuDw="],"bias":["ggUKvwnFK75IuQ8+dvXGvZDfP71wXVG90r8ov5B1hbxr/52+oIh9PPjVDL4wLpK8UGffvuYGnj2aWUO/YCGWPYaBSz4LEzs+i6kuv7jq/TwVKVO+/2X7vn2W0b3oq5Q84gOIvv7oML52nQq+6rwivRx3sj3ABIC8M1J9vrgH+73ErcE9FOTTvViMWT4fRru+oPPMvXJ9gT6ggJQ9/DE0vbItQj7X8Ku+APeWPD4XPj7RKCG+lx6mvlAmu7y0ob+9ci2zvWS2Z7200Tq+AC4MPCEZer6A9te9vHDQvajCo73yO1U+xLBZPX78lr4YmSy9FGSHvsiaLr7Om/a+RHHWPUiozrwCQr6+sDHiPQCEpbqYE9K82MuQvSYY6b5wWOu81n3gvQTZ1z08p4+9HLeVvYqBZ77cjYG+mKBbPQjq/71kX4u9nP1bvjzMrL0A6qo7YDetPaDACD76llO+zyPqvQSQmL4cjDg9hH5xvfgcj74SEES/QN7OPaDPgT1FsCM+ZTSivnF4tb++17G+wLGXu0KWF78XTr6+xh8wPmRMlb3gZE69pqynv+CJRjwWLRG+na+qvTM9mb7IsFA+LMwCviScFr6APL29yIvrPVzG871suDu+rXWJPoBCGTsQb6q+0Fh5vdCI471oOiI947rMvgAQ3zjE3h2/VWtGvuhlNj0SzRc+wOmkPADI/bnl3C6+qOWhPndYQz5z0DI+6O8lvU7qOz5H94W+n0VqPnDUKD5QDVo9q8ABPs9rDb4Ao3Q7GhNavmj6LT5EJDo9+KXCvtINkL2A1TU8rET6vWgi0b4gpp2+ADjVvC5BML6W4AE+jNs0PfSxDr2KCoo9aus7vhAj3T3Y4pO+jmACPt6FIb7+Dwq/1J6XPnDCnTwQaUI+kIdGvYCCnDtQtDc9WBCLPE7elb3i1qo9+PGavQierb0cehi9ZjW3PghS370Aczs7Tg0YPpx8hb6MBoG99GvKPUTxCr5gc4W8IGw6Ps2YVb6G6QG+d2uhPlr6Sj6A3k08","Yt8FvoB6pz1IDzC91Dhgv1gejT5iOUQ+L9AWPlIEpb7ztpy+7pQvvt5bXr7YHGS+MIxxPjhy9j0Iyuq9QiMfPgxB3z3M0q6+4DfTPfDn5jwomYY9dBLXvSDJmz19wRm+PB+CvjAhAj0HNES+UDRWPawkJ76wLgi9UIXsvaNeEj7H9rG+dHxXvcC97b3omtM9T+yZvZUeeT4A3vq97KmWvQoYAj5oSoe+SG2sPci9Cb2CxuW+m3BgvqAPm7xGMfm9AFCluUolHr4Qtxi+P6yDPvLt0T5gkNE881KoPgmPIb4wLCo96OSRvDCM1DwrEJ29EYRovgBGnzxEiBG/uNC+vQ=="]},"dense_4":{"weights":["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"],"bias":["UTXDPjbnEj+6NQE/wAE0vH3Haz9PHZQ+vQsFP9JHBz8Yexg/u74nPk3MKD/iS6s+VgOnPn8bwT7wohE/yTvkPgRftj54ktg9hexNvrRRAD9T1qw+bMWaPmQOCz9mtJ4+7+BLPy4d9j6KciE/4AMaPywfhD6JNL4+FXp5PlKKvj5IMRo/TS6mPugNFT9ezi8/eS0ePxHjyj4NUJA+zwujvjZbiD7UiQY/8xDcPhkcJj2cd4Q+pjpCPq+9dT4lsbE+m3t9Pgn0oT4YstU+q9WtPhxcsj5tD4+9+9cwPnHQ7D710P8+fnXzPsUQ8j68kdI+1stjPoVyHT/KcZg+TGzjPQ=="]},"dense_5":{"weights":["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"],"bias":["s/H+PUyeqD2Vy+y8cIlcvhreGz25S/M8WWuzPQjJbD3oaBU+Dz3AuscIAT3MLdu8NfWiPSUPob0ONyE+EFiwvShgkL2upj29F+E3PS7rDj6SjIE9gDijvdLO9z0V8O68FaI3PQESzz2ajyk+JtC7PUJ27D2v37Y9roXsO/yY4r3N7FU78MRHvMo7ZD6sUtk9ytWcPVoDkrxKP9I8NkTPPATwnz12Z9I9NdYBvXuZPD07FRk9XSSFvFrDk70/x228oOQmvayHED6KbSA+J8fWPYHw1ztHtbw90Q7ePRiFZT1N+FU9vPd+PT0W7TsR7IQ9qeBavJb7hj3K6mQ8kfsfPg=="]},"dense_6":{"weights":["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"],"bias":["XXYIwDdeJUA2EfU/WEmfvd/Ag7+nfYI9ulIHP2OGg79qz6i/2Z6oPzqlvz/e4yw/8Br8P1zvAz4X+NO9ffaQvz/BXkBcGKQ+SiP8PyGO+z+1Lpo/oAIRQC4GHT3cgAdARi1JQMF2nb/elb4/s/5uPwgnuz7+XgK/azXSPypqHz+OGlBA/oUmPV1VLL98lwBAAKERP8uSXzz2NZg/yo5iv5DJAMDoK0dA1O5vv2XdDsA3FSi+HkDIPg4ih7+w9L2/vvcjP/EyrT/sFkm/c2HnvzoWxz9TO/g/QmAYP8Ocib9sPI++OCyFv6jbIr88VU6+8waXP/DfoD59gMw/WU+kvg=="]},"dense_7":{"weights":["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"],"bias":["8YKIv91oYT9dHam/SdVCv8I9EMAzLEZApvSGPz+5bj8tzL6/FmQXvxtjV0C1Kkg/eBbaP6JUnT9Lauw+6PsYv2fGe78jAZc//R/3PgkySMBXGPO9n9c0PvC6gb8FlI2/MZm/P41Hjj7c1Iq/PW4Sv3jcPT89ETPAJyXxvwgtP74="]},"dense_8":{"weights":["OcTCPVtjJL4aAHU9kGN9PTOa8j2g5qU9KAonvmPyEz6lSMo9LX/FPTNQJT6b1kQ+HCv4PS+jgz26Yye+HYiWPbDuQzwN8bA8nO1pO+sW9r0QU+o96jSOPY8Cr7zpyyw+gt0/PCCQMD4/io0773OpPRRgk71kBOU9pz/7PZU7qTsE+wm9HIMnPh07mL3K6Yg9ODo5vlxotD1MfTW+UcDPPL/tWT7IFM88GvgvPvCFnT160L09FvSTve0CpLz2rTu+gSX3PIkFNz4bzNc9s+suvi0yHT5o7Q0+FJ5MPrylAj4L0jm9IPDUPZ3cAT4Zd9K9cVcEPnw9Z73545c9MgDBPRc8lbxioRK+GMaBPAToQ72RVV4+oQ6Cvsdz777kBvK9G/dqvQXoPj6lIjm+G7gCvjrSS76Nz6y92QmBPEsHRz5IkX8+wrAZPlZqBT1AYBo9iwGoPSgE9T3YHd+9XBxavt2LUzzndhO+J9E2PpZ/eTyaq7e9LUNGPvnaID4AN7i8qASDPQUvLT0js8m8+MMUvVFZ873KiLC8xNRxP6w8Br7cMAA+5bxDvqCbCD6KFw0+mgMgvgHU4b0IwiI+w4EdPjCdDr1x55m9gfL9vMi10Lyw89Q8Pw3Tvf76Mb2hh3+70NV4OxHn9L2S6PU8SRfTPSm4Cr7POw885uegPMIlQb6bYii6atLTPSypi72K9Si+LMSgvSv9Kz4Uva09EuKVvZ9GPT6UzcY7E9ORvQvbYr1DCNs9LfpdPq1bAj7oC7K9G6KmPVaDZT3/1y2+RwaiPVFvWr6KR+e9IPV7vUZLoj3qFS++MH6uPpob+DyEtRg+fwGpPSHT8T1Dbxg+ihX6PPYFIb1EdxO9HGdePGbnor1yzNS7X1b5PZbPTrwSop89esmnulfMzDzr0Ds+y8VfPo87jrv13xK9VF4+vtjT5Tyg+cw9owiYPaAEHz6weYG9Bz6tPRNNz71fJeQ8PwUwPgBmCbwhsBc+i3BOPmq67T39pYo8hvC4PUi3gb3T3949","ymCZveds4D3nzTO8y+vjPZe7SLypaoc93Zc1PZG/Ub1mCwC+SnDKPcGYCr4WsQM7mpKtvStt3T2KBwG+2EsyPuuwx70QDi4+FghxPZdZID4b2e08g/OQPc8+oz0C/uI872D1POEvaD0o9y69F6OWO4ZUv71g2A0+9A7zPc9KMz6nIFy9Rm0FPpGE7rwHi/G9SazHvZrkYz3W6Ve76EwbPr8TdT3rqRk+IA/qPX6gPL21Jx68uT4Nvpb+Aj70nag9KkMEvRayuD0xnPE9KpM/PnArQjwm2EK99RSyPl1Fzz0vgz89XhxnvbWakT61urk9ISKzPejkyb0P9SM+m7Qsvhm83D2yC0M9XgI7PiWSmr0h6XU90mjpvKVlcr2OhTs98vgHPuuSYT4m34s9t/TsvdD0ID3EVC0+K8o0vqpbEb30kxW+MTbqPQj5kDxmgU8+m/yjvfPeDb6EVV8+nhE9vK5i6DwwEKs8R9uOPXRoz720XPI8t4cXvmW4KT7YHrO9txcUPnb9i72da/q8HuMaPklibT2BsHY9JpoLPhH0jT2o4vc9yIQDvfl6mT2EL38+H528PcffMT4iMiE96DoPvWHBqbzFnMw7z5cTPZAxzb2etGG9r73BPNNKmb3o5UC8VMdhOty4MT33+Uu9ym0YvW/yyz1J+SU+ihsrPuU0ET5zTdq9w5GUuxs54r0MT6S9kkfqvSdHhLyunka9hT14PVRlQ71nO3092vnuvGNijb3hnDA+vlVovuxgGr1YCvM97R7mvbkSp7t6uOQ7evBAPk5Jfz03Eq+7FrGTPi5HtD4e46k9Dg19Ped/yz01fYk9ZlV2vcfBJL6ZC+28haj0vV2lOD3xAUk8bSRXPCVDFj5iDCg9tZaNvQGYn73EGKs9SKt9vdqABj2o92U9fwsGvhrmzjtiDRc+JjYnvuPwLD4qeLs9+Mm6PQdNOr5LGdo9FPARPGT2DT4QvO292A1NvrS/QDwlLCO8hvcNvbPS2D0JkaA9xHCePG0hXT7NWQ4+","FFgavZF2GbxbIO08ntsVvV2whD33Oti83wqCvnWkTj1zDU26uOD4PRtJ3j3zBQu+anlHvY57RD4H17e9BzXGPeUterxJBgc+p+Cxu/aVmz2dP8G8i0OcvaN0Zz0M8xQ9FZIUvJEXtD279uU9QdE9PYmHkr0K1cC9mrywPIvebD3yGMg9TE+pvb9jsD2vXAO+ynEUPmraI73vhTQ+2JWMvLAHFD4ohFk9HFdJvckUHD6jbJm9ydQwPjXyA76nvPa9xdYuPk4EgT0Pz68715CRPBoylr31Nh293wIBvivLHj7vr8o9t20EvsMe+D3c7LW91HCdPQiBfT4Eei481rbVvdAhDr6izbw9mGdbPUt0pz1w0Lg7ll3MPWiOCr5u3qE9IXrPvWbE7T39bwA+/nhDvUZqRz2m+yY9vyWMvhheMr1E/tY9CjqnOhRVHz1TWC8+pYNRPtnP2T0exy4+QGkTPmxdHb1Mo6w9M9ViPIuCFD5AhF29HJMhvPSn9D39KVw9fV4QPbbN7b4g3QC7QlVuvn8gGj0XMX6+dAaHvc2sFr6lsVc+0IBGvVwFk76PBfY9F1agvc2MhD1xDhE+OcOGvVE9Az5irVO9V+AIPqYxnb6whTc+TMe6PBSqzr2j+7+7aMJaPandNb5GBTk9HJU4vnuCr77LrW4+bVIFPkdJ6T0GHbW9ym+NPV2ta73g8s+9Yz1aPi7Cbr0Lw6U9O/PYvdFknz2+rdM97RIavlh7AL7zYy2+hoIfPHjd8L1MPiM+54TJvFjPvj2Hy6e+TDevPpdszjzun2Q8zk+wvTYfiL7Q8CU9Lg4HvqeZ0D2618u7OVg6vujX9Lz8/IQ+tmsqPbDECT4eRPw9hJDCPMYUG70kRos9vu8YPd8eP7wOGJC9drExPau/Dz54qDq9TCcPPQZj3D1BWJm9wlcZvghw5z0nfro9TVbmvRxnFz4h6989jmCivQbBU7xmBpo9ywkaPhPQBr12pK09P4z3PcNv2zxqWt+9AjM7PvIzQD6LfgO+","IMQzPQYMgL3iqo087293vT6Uzj0+Lai9eXWSvQwkzzzDkFE+gd/hvaGZKr1Nl7o8wPz2PY2G3D2nFCW9zNvQPQKnqT1STJS9+drEPAs897ylHiE+xDFgvHW2Sb3DzAw+p/D+PBONh7yzEf88vEWMPXYX0b00CGw9M+0vPnEHtT1Yfo89S+dOveE9jzkiY8A7h/YCvvOTgT1f4AY9FKeSPRP2pDsFJJw8wM6EO7XuHj6bNUE+PyIlPVAdU75jSPe88FmMPawDxzx4ZYI+aKI+vnUtZD42kRC+SYEtvCMWHz4GUIg7PAcZPhWO1j2A5Sg+pSELPvXPmj2297+9Sm0HPtIEGT4+OIe88u9hPiiNsD0vBhs+fo0QvtCcmLyL0p89AEo+vJKTkj3o3Fg82p6xu8xgRjuZCyo+G0H5vB5TZ7wK7h0+M5p/PV8bI7061d+9LrYfveL5wTyMkSa+ISQgPiTVazygteE9WcMRPYopcT1eqRk9IOjMPC/q5b3SvAg+6NMjvSpykL0rLS29FjKOPWAchj4pvBE9/fh/PHWHlLx1X2O+m9gFPiVnujzDglK9v4DsPKHjbD0/U7m9/0q1PcANAz6sg7I9K4B3PWLUCj1pINe75T5NPZjDOj1aSr+9nj8JvfDeNb7xDcA9bizuPYgP7TsiWUM+P7qhPene5z02yBQ+xQM2vffGbT5QqZy+zYANPh/RFb6ODg48cQISPj4XCT5ivYQ98yFsvaNNpT6Mcbu87SlDPFZdFL1gkNO+gGANvDayjL28+Dq91RZ8vo3RYr2lrzo9RBZsvbrit7ykoEU9Ua8ZvRgSyrxcOrS9n1zOPfeTVT76Xk48JsSfvTkX1D1d8qa95XYOvkD6QT0/m6Q+Y8+gPJ2uDT4c3qI95wOuvck/fzw2sWq9i+zGvX3hpb0K34S8ODRFvX+bDT6TQI69mvYJPv0hh755O4s9IZo0PqHq+T3YR5y8V3x3vqG4Vz1+VZe93Y0XPcKtYrzJW1i9NcCCPToiOz6CZ528","RtG/vGHR87x5xEE+umrMPFZDAD7ei9a9FvesvenRGL4YtGY9g5/2PZLFp72cwxm+PujPvMuWEL2APw29UXyKPSlvEj6gs6E9ZN3YvQYbkD7o5US9jcdpPjTdEL1O3hG+473evYDwoj0crue8PLo7PYnRzT1YzO69uvbvO/YnBb0yY5i9X+lXvNf9Mj7uzBY+QwVuPqRo3D0xssg9mwcvPshtab5Akok9ZIuIvXVVf763G829+27CPdglnL2A9wU8fqABPkhD0T1xaS2+bHpYPi4AT7yuFxI+4DTgPa1mu731gfI8vBlvvCdb1LwwEkw8NpIePpnpez2e0xU9OzJaPk3oSb37RwM+Nd4+PmQrYb52pSy9R9YoPk3qjr09F2M+T1sHPaM6BTyqHVs8isMtvVKuHb3IRkU+d+IGOEoBWT17P/m8E4EFPj+l9r3GQiY+NEQxvRtk8rydyFw+kdpvvmjxOj4zNCM9r4oVPnJs5rs3/js+X5M4vn7e870Y0S0+nED4PT189Tx61Ki9dRgYPo6WwL3W+8k9hTBQPmkv6D04q22+zXzUPUpNGz1Wgt09sbkdPlanQr3CU4O9WQISPpeOcjyhrt06pXD2Pd8lqjxBV/I9iWcFPhu9w7z0mjU+DS5GvK0ZGz4r+wI9uj7lPfxiDj4lVKg9tWsCvTeO+zz92Lg7pizqvc6bHD51NO08KNUgPGVgYj7MiGm8ljIjPluIQbxx+hm++IxoPZkHTD6qoYs8Mj0HvuywFT60OBy+Ze+5vbI4iL31lmc+JmgJvvc99z0/raY8K0X8PZY6FT6Hsl+77of+Pe6QYz0HoTM+BzT3PdHn+LxRpZa9eGi+vRMKLr0NCxU9Yaa3PWL6Q71znPK6yz8NPlgfq73POcS6Lx/mPBU+Fr10jK08INZIPXSbJr2ddmM8iL3/PaezGz6k9S8+7UgBPjoKqT36CPE7u4yEvG0SOz59QVg9p8PNu2DP4708FnE8OR1HvfcA0TyS/j4+d1AHPqVyNz5g5yk8","VVt9PYyg8D1qCbK9eKQAPasMNL1Vlew9gsgPPUQ+m70suFE9kHlKvZksVD5YVcC9AxVSvbxYHLvZNqm9l0+DveEwZr4igZa8JIgaPuniuT3AZUe8vTMIvnfcRT4NZa06iIyVveYyGT5SZlI+kfELPmlVHT0HKpW9IQNzPPtagruAYSK9gurDvKrjkb2VAEi9nEVsPVb0V72ZEgy9jec3vUHDhjzJpyo+xaOYvYq9Fr7zCSi+lv8FPoUGOj4RWik+ddSKvlknxTxrnTy9qAe5PSEzWL2qc6A9tBTuvAO1t73P1Sc+octdPeTBkj28nOU8bkANPR/o6r2eS2A7Il2jPXMHcD4Ivym6RAliPZqC9zwepFk9J12RPaUXyb0YJGU+DYFHPTIBgD6nB6i9g61IPXkCjb6rCxa+kJlGPAMZrr6S1lo9PNNpvYhE+b39afi9pHDlvA8hFT4xf249outTvM5jgr0wese7x9EjPmRqIT2IPOG8hUCQPVXUdT4EsKC9hm1dPpbjBL5i0C4+InE/vH57lz0aYa08wRZpvp8AwT0PT6M8VLsEvLR3A74/vHk+Y510vu+eITr7Pzw9AieLvjFxF705QoS9dfARvVwGgb1LhsG9B2ASPhrqHL4gGCu9TmB1PbrqcL7Dwjo+VPJGvuIICr5FXqI+Pp2ZPsMeOzqYBhM+CZ/hPWKXhj1xiBE+x06cvJwHCz6MA8g9nOIbPE9i6L3nGVm96U0xPo3habr2cro9UDm/PamysrxpcpM9Js+RPea0WjtFjok9rDybPXNQfj2I+Ka83kxFPuwlyzz8EqG9FAEKPvIUrT2uju895nwQPr9xJT3oWz29IekmPTt8S70OURQ+wu44PkpM7j28HEk+dh/cPYT7R75tVxc+z5BpvukCQj3S6Pm8aniHvAkT4T1LMxI+MeicPR0ILD4Rlou9FIQOvXgHt71oiiA+OKv9PM7hKTxPNr08Ci3YPWrk9TswHaI9T70QPiPYAD7rTVo+i5n2vK2Aoj0urNA9","P7x7PQAzzj1jkyM+PFs+vgt2kD6YYt69GTT8vQRfPD4vYpQ9SWtBvStIT74TyBg+lXCxPAncMz7HQBY+JUAjPlf71z08vZO9y6DpvgXkVDw6Rec83D9GvSJ5rD3pclg9NtT8PVHa5z3qjtE9FK8JPpxDFD60yES9aUcbvcGx3T2Z2gy8WogYPgVJEz7XVme9qlR/vabq0D3a2bM9QrMVvU4QZD6+DrW9tkeUvaD2Yz7/uOU8adnvPUOHlL0EvQC+6fekPXnqFj1A7+I8LrU2vVWEhDxs+qG9iXFNvTHBrT3k5cW7GMq7Pd9btD0Y4hI+X5IWvRCtLz3uyA47+xyuPVwN2T2P2bi9g5bvPQpCPj0FPTM+bWw+O3coxj0QbAw9I8eQPfZmKb2nQps9t9S4PeOH4jxsMmk+MjDevU0stD0on5c8Uv+fPQxoSz4CoUI9h9ShPZtgCL2guxU+LnqDPVuwhjtp07O9mF5pvGcAu7qezpY9uQ5mvZ40VrhEny0+D2CRvf6wxz0yMKu7bzX+vV6Fmj3EQ6q94jJ3vmo32b1xH6E+gOEuvpcM+LvO1ek9J5+SPcQAAj6Zdlw+te4BvmY2Az5gB748K9AWPVeBs71Q9688B0yqPYAdgTkqWzw9ZpztPQSWy7tDKtE9FOSTvaZjKb6dyxk+N2pcPTm1ZDvHrgG+5pNUO5NFwT09qZM99Wz7PduCab1N/OE8mEyXPTdQxz0ig1w9iQc3PvdNGj5FXjM+Xf0OPi60Rj27J8E9RlI4Pf6tQ73qPbw9wFOFPZ/7ET4vxh0+YgSbPcrTz7rAQV+9AvHgPTp3Cz7v1MI9sTkZPZNI2DzSkoa8oCMhPg0eFb0fydC9Xy7ZPSmSrz2O1IC9SoEpPeFTS75dQ5i8SkIuvj5NXj08WRo9NYvtPdHBPj52diu9CDZwPNhQnT1q6We9lgMAPkLItT0Pves9eVP9PdWngz2DPSm9F9b2vEJXd70B9AU+uzQsPSj3Cj4PIx8+k6GUvRcRkz30s2s9","ulEkPtqbnz1r1Da+rsl6PvGQjL3rzCW9bU8YP3K0Kb5WkXs+6BM7vaAcWj1WqNW9g/v9vexyjjwhKdI838oGPhO8WL3D6MW9jmvKvVZlzzuqhpw9gXyIvUvwdL2rbN89h6CkvU1DDb6mtjE9seAOvnURFL7IJeQ90aysvHIRVr6J2YS9blQfvTnW5j2VurE9YFb3PQcE7r2MTRo9K2D9PRoOx7sv2I+8bhtCvWZQIb1RDOM9GP/MPY+BRj3J8XI9CIByPfjgMj7irJ095i1cvRdP+D3kPAg+4dBUvLrf/DynGIU9awGfvdsfPD5rCzg9vMqQvcGFcr2wt1w9dDmvPD6FuryIx7e7imykvfCEMD4Ly0k+qKzdvH8Rhj3/Avy90MUdPcKTxTyOV4u9x50fvfRwKT1RsoU9skS0PdGBkj13k4A+LNWcPGcVEz2+E9y9dSoKPlcxiT1yVl68rzQfPKgjYzx9YrQ8hV61PJtJNT4w9qi9JIJ1PSDYV72COhY+3ls1PLjNRb7nKhK925aNvnpAGD2N+hW8opzGPH/LzL26cd49kJ0KPp+jVr4qC0A+GaihPLHWm70Sf349rf6hvrRZIz1ZoOO94pfjvZIMMb7QdvM9R9MHPu1fuL3XB5Y9U75MPWLcFr4LTp4922+HvTiomL7/ahI9Iwf0PdNk9L2WF/K96x3JvPR2+733uWw9IT+NPYf0sTveKhQ+VtJWvcfDe7zAxT8+Zs2bveTNFL6yAhe9fX8rPjRKPr1IRyE+N9nxPLbzgzslAx8+9q1xPv3MdT1LJfM9xkqBPdhMCb1HHKQ804FMPcjaBj1/UKo92/GXuz/oED4wuEe9zX4UPhpiwD3hQQM8VjcHPuUj1zw7gYu9XAfqvAu9KD74xEy9nWksPlt6Eb5AtZw90YJbPrQ68D2cwvY8UZ+0va29Z71vfBE+SZOJPaPeBj3+ZDE9LUHzPRohcz14dC4+r9sPPgLrzbx1A289uq0aPtKWwTyQ0OY9iMbiPeSuBz6tN5u9","b2yWvA3EpDtdkwQ+JaLWvNL5AT6b/Is9UJg1vjRnfb0Ar5E+ec8bPt/K6L1nB0s++8cgvpuX3Tx54kQ+FpCHvkOAtbszjRS+ropUPR4Ke7x6M909rNLzPXaozb1v0L+9vNIaPQB81TxEhIw997oYvodSM77DubY+f5w+PgNJ6DtVuvg9TIoYO5CMFD1B4cU9l/bNvYGncr0LITA+9uGiPSxL5zxCcO29Dq28PVkpKz6XtFy9tJKzvHPEn7zLHKi9ESGnPa4Z4j38FCi8dEopvqxWBj5WWPu97yuSvM1VRT7SA+g8pOXBO5HfID4hskI9MUdHvTZlET7ly3e8XpfEPNFXsT2+ILk9niNgPTZSGD7BSwC+1kzUPH02OL76AcE9JYZ2PfT6DTqw5nw+NU1IPo+YBj6/1LC83Tb7O3snrbpUgPk80JqBvWzR/T0SnwY+rjAvPBxpsj2VsYg+51CVPYgYkb3OElo+h9YZPgipNj4CVBM+h4G7PeCaob2PckU9iu2rPZ8Alz02wm67f/yVPYcT+bw59uM9kKebPnHdbz3hTgs+aMK9PPyg1jyLxR8+hZQqPrX1szwkgB6+yaNxPVgWcLwWcpA9B1JaPiitwDx5w9Q8s7ctPFbwNT56VqO8s/YFvQahAb5SsiE7L4zgPFb/Q70hdtY71/QgvQX62D33GyK93HBOvm0r6TxV+8E8ITFXPvsHPz1JEJe+gDHYvCb1Aj3Knyw+TYjJvWZq8b1y+5y9znTVPGzC3721VP08aCE1Pfkf+j0/bWG+KdZ2PmZK1TyG83g8q88CvsV6Tr7I0yC8/DThvRrMkb20rEU9/5bUvacp/z2NAok++QkKPuMZ5L1YZoy9ZpkMPBB8uT1QbSs8666+vYyzUT22cZQ9EzUJPgvi6D2RYTy9NhhtvU7D/LxEztq81sgdOtZ8Fz7n2S28yRm+PZA0Ub3FYh0+Am5FvdKVxT2FINc9V3YUvWuCZrzhl5Q95wIiPgyMCT7Ou6S9G4SpPSsSOD6Bhou9","wRfOPJ3v170EPVe9F79BPQDQQj5icNU9VuErPvaIUD1sAjS9M4MmPvvBEz7rmem9L/dvvZ8xlL1rp/69Xjg9PnCllj1oHAY+hiZLvXNojryBW9G9S78rPrh3BD55sJ49I8URvfagkTxqNoi9ZIGHPT8AAr5Y9909vl4uPkmt1L3M/749HrepOth52T2boCm+TBGRvZWssT2srU6+3RsGPQAktLwakwc+C+fyPbniXD3tKMK7dDwBvm1RYr7uS8U9TQyDvgP7LD7/+ic9F7oKPgF54D2qWry9jHR8PgVblD0oQL09dhXWPR7YFD69URu7CfxpvfJblr6dZew9lECWvCYzFT6GIky9FkAdOywYJr1akKw5MPArvp/sl76UVAA+ddKKPqZlVj7zbgy9gCO0PXJWhr0P46y8+0jSvtsH8L0xobE93mPUPSaAtr0NvC++z9aaPafxH73Zzw6+jTz8Penw1L3IW+W96Rs/vZBjV76rgfq8xk0aPmhIQD2fxia9eU/1PVrFi74YVSo+4zO6PY2DUD4knZ69K6KlPa9OLr2c0Mo6J+0RvnbvID5xChk+2fW/PDv1UT5ERPW9RuATvEfCGj2v4CE+sehjPASFlr3rgJ49OG+CPNHqu70mf7S9u9VfPFzUjL1165q9srUpPhZ55TwJaPy8g1zwu5dbOD4CAAW9p+isu1Bhxzynfe67D0tDPuXV+T1b1IS9eRaFvUs6lryAFSE+3olUPpmjHL7OFpY9BRQiPcxZUL0e4A0+U4GLvSDAdD0vcK49ZT47Pnwgpj1NAIG8w+U9PmL/pjykk9U7SQipPP3Wxr2Kw8i701CBvWN5lr3di/o9OzsWPkL+nj2DRWe9EjeRPaTNnD25lCm92TSqPf+dWzuTuoa9S/MTPc0hDL4SxSQ+BD9dPvhcHT7COL49rQ9tPYujsT0IOmA9vrPWPUfoLD26MIK99dIEPqJT7j1TxLK9J9++vNDv8LymBnQ9es6/PCXNPT4Qjbs9fvx8PcSMij1fKFk9","uoKTvM4F/70w6RO+g261Oy9HlDztq9C8E7gDvuEOlj0wliK98PsKPEgomryJIOy9j/rdvIFltD2G2fG+a62RPY/8sDs+Wp68PLoaveealD01zhw9AR0wPfhFuL3PVcG8mmSbu/cUSL3jZA4+MOYcPWRQDb7Bnks94BLePZQdqj2s38y9sYilPQ/KHL2ip+s9BS5qvX/fLz70wyA+ptZ3PZnP473CRBS+vt7CPU1ZQjq1J30+EIbrvURxVz6c5uK9VKwGvj2kED2I67Q9t8ASvtDYeD5Yw8w95rNfPkn3jz7h0ze+AhZYPv/RIz5CkCY9YowpPqX9Ir5HFA6+qnXmOk4gq74lLKM9942ZvXlxg77sDzm90lWOPbpdP77fnva90Z6xvXkHi71rsbe8ZcUAPQXViT4BOW6+Qd9OPlQem7yrlHs9Fay1vczZUr0YyjM9//j+PftdJT3el5Q+Hg1aPBhogD1rbws+dRSDPhMAwDz/+/y8fKJivM+qmT7hvE29NJ9avppuGb2ifoW9+8ZwvRc+MT56Ewo+c3c1vnH61bwCo6O9Loa0Pe+2kr04Xmi9aOEDvZVEib5Gm7M9QPwuvTop77xIDR8+C9neO0JE2z36zh4+P33cPcRqaz7us4U+eH0lu1yDIj2Er8i9QogiPg3Hvz2wkLs8b3K7u9MCyjw="],"bias":["EARMvd8ZND6aswo9p7DBPEByUD0pxtC77iTJvc0FmDwo8k88OHfFPJxw3DyDr3s85rsivHWOuz25kaa9jl7XPVKy0j2Laws98i8FvU7lWL0VMx0+1f4WPnPjAT4UXU4+/7vNuoWLqbwCD6S8VwvRPS00Gj5Mq4u9VKdMvXlh+D0="]},"dense_9":{"weights":["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"],"bias":["wwbgvRSFQrwKv9c9LLXYPE9m6rxGEyC9ESrDvRZiX75hcna9/uKrPZkDhz01UuG7+ykCvteNj73V7rA8T8vcvR+GAL1Zy3Q9oS7nPbl3u70="]}},"hash":"45e2d89f636057d556ee6d89d417326d9cf91928865fbd4975ad9d8e5f425439"} \ No newline at end of file diff --git a/src/kernels/gfx942130.HIP.fdb.txt.bz2 b/src/kernels/gfx942130.HIP.fdb.txt.bz2 index 79617e9598..2cf726d1c8 100644 Binary files a/src/kernels/gfx942130.HIP.fdb.txt.bz2 and b/src/kernels/gfx942130.HIP.fdb.txt.bz2 differ diff --git a/src/kernels/gfx942_ConvHipIgemmGroupXdlops_decoder.ktn.model b/src/kernels/gfx942_ConvHipIgemmGroupXdlops_decoder.ktn.model new file mode 100644 index 0000000000..dc6ef494d2 --- /dev/null +++ b/src/kernels/gfx942_ConvHipIgemmGroupXdlops_decoder.ktn.model @@ -0,0 +1 @@ +{"architecture":{"class_name":"Functional","config":{"name":"model_1","trainable":true,"layers":[{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,1],"dtype":"float32","sparse":false,"ragged":false,"name":"input_2"},"registered_name":null,"name":"input_2","inbound_nodes":[]},{"module":"keras.layers","class_name":"Embedding","config":{"name":"embedding","trainable":true,"dtype":"float32","batch_input_shape":[null,1],"input_dim":60,"output_dim":16,"embeddings_initializer":{"module":"keras.initializers","class_name":"RandomUniform","config":{"minval":-0.05,"maxval":0.05,"seed":null},"registered_name":null},"embeddings_regularizer":null,"activity_regularizer":null,"embeddings_constraint":null,"mask_zero":false,"input_length":1},"registered_name":null,"build_config":{"input_shape":[null,1]},"name":"embedding","inbound_nodes":[[["input_2",0,0,{}]]]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_3"},"registered_name":null,"name":"input_3","inbound_nodes":[]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_4"},"registered_name":null,"name":"input_4","inbound_nodes":[]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm_2","trainable":true,"dtype":"float32","return_sequences":true,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[[null,1,16],[null,64],[null,64]]},"name":"lstm_2","inbound_nodes":[[["embedding",0,0,{}],["input_3",0,0,{}],["input_4",0,0,{}]]]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_5"},"registered_name":null,"name":"input_5","inbound_nodes":[]},{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,64],"dtype":"float32","sparse":false,"ragged":false,"name":"input_6"},"registered_name":null,"name":"input_6","inbound_nodes":[]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm_3","trainable":true,"dtype":"float32","return_sequences":false,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[[null,1,64],[null,64],[null,64]]},"name":"lstm_3","inbound_nodes":[[["lstm_2",0,0,{}],["input_5",0,0,{}],["input_6",0,0,{}]]]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense_1","trainable":true,"dtype":"float32","units":60,"activation":"linear","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,64]},"name":"dense_1","inbound_nodes":[[["lstm_3",0,0,{}]]]}],"input_layers":[["input_2",0,0],["input_3",0,0],["input_4",0,0],["input_5",0,0],["input_6",0,0]],"output_layers":[["dense_1",0,0],["lstm_2",0,1],["lstm_2",0,2],["lstm_3",0,1],["lstm_3",0,2]]},"keras_version":"2.16.0","backend":"tensorflow"},"image_data_format":"channels_last","input_shapes":[[1],[64],[64],[64],[64]],"output_shapes":[[60],[64],[64],[64],[64]],"tests":[{"inputs":[{"shape":[1],"values":["AABQQg=="]},{"shape":[64],"values":["IRmCv05dqz4zQ3A/pZkyPtRR4D5akPy+3CX2PhKEHj9cVVW/NRKCP210Ej+4v0I/hlmkPn/hC78amOg/VsN5Pi7O0b6KoiY/3owfvubNgb9LqJg/dJY/P4Odkz5vpY09DUsYP+2wk78KVBk/n/64v8c8sj6RvJI/ZKiFvqEX6T1llXM9h8LyPrfU7D9B76M+wJkZP5SEKkBPMQxABpDjvhCVsz/4P8q9rghkv0pDgb/c9oA/Qt/tvq6HFD+pSqk/0gKpvw4mwr99aCO/lziiO4SvM78wYa2+adqFv7uOIb03M6s+z3Gkv3nH+j5olse8wL0FQOG7kj/ouxq+wzG/Pw=="]},{"shape":[64],"values":["RAAXP1Lupr8kFYO/wo2BPUVQC78BwE0/MNajv+hkij7rr4e/d30cv8Hgez/ufFk/s2BRP8eWfj+SNoU+WAlfv02P6r6ngMK/68fdP5L6hb6aj+W/5maVvXWymD7OUNg8ztfsvtxM1T8Xi9e+VQ7iPx9MFL9x4JA/2nWYP5rBmj/WC6e/lBhjP0po1z99bre/iISQvrliJT8X6I6/nFgrv+RpLD5rF94+f/uYv8fqEj3hJJA8SiPNPxxRMD79VXE/poOhPVzlhb9baaE+s4+1P8Ln177ZcwO/YoLPv1w4CT/OL9K+uS2YPiTNq76l9zk/DjQdP3YPrb/Y83E/UfeSPw=="]},{"shape":[64],"values":["MIoUPib5Sb/ILUc/2M1IP/D4ir9tyGg/wwdqv6Jsk77d1yC/PfvcvSgKED+r7JY9wq7XP+Eerj9h4JE/UwtgPySDkr7Sa84+6ivLP4ME9b/IhaI/FedZv3BTWj8Biae/xhV5P+SE/j7G/Ni9TbsLP03tIT/aDBm/EEYUvnGHNL/k/u4+XJUfPj7kpb9ZbSY/k6C0Pi3nAj8TOUa/qLfKPqZ4yD2eXPG8F8P0vljPDEDrf6y/6R6Fv1vcMz8/sXu+gavmP7Dfdr9uAwk/gaJCv1U4Sj/1+Ga+SI4eP7pZp79aXGs/to0Pvr/st7/4+sa9nVravmnDWj/Ha/C/fsCXvw=="]},{"shape":[64],"values":["rnyxPwF9dD8E5Po9CTcAv7cuYj7or6I+rSWOvEEmcD6kOAjA/+ypP8bwHb9jSnI/tP3+P7Igsb4kHm++VodOP0Knoz53qzG//Wg0wNnIdj1FwT4/GT1gPktXCj/q3sC/wFJHQHZUyL+tumi/T1QIQDosZD5e3cM/boLku5BuWT5Klx6/1DGeP7Zi7D96Alk/xDASvgruob/gRA6/F6mjv4+Z1z3rWle/vZksP+BDFz+xyLW/eb6uPoecG79Nv8a/ZNTlvsN9nz/eeUK+XZptP1slAb8475Q/6ynQv1beXT5nS1A/UXSCvgL0qb9EUMY+Ikvgvm8T3j/YRqE+76eGvw=="]}],"outputs":[{"shape":[60],"values":["8Bp1Pedvu7/mFNE/cUx+P/vY0D4QYx0/LxyKv6z8BkBVo4q/BFSwP5358D/O2PC93Pj8vyDDUMAF4z/AnA7CPxxpAb+BOoE/2wGGvHofdD4Z1i8/l6aVQPUMrz8C3VRAQ2nuv6C8SUAdUwdA94HKv42BGEARE51AyD16wJ21jb+hVxjAaJ2Qv15HC8AqzCdAB7D8PxRcqL2BJgw/D0pDQKienD9unAVAailQP5reCr9k9IE/hbHWPsDrlL2JB+8++hg3Px6Mrz8ANPQ5B4g+v74zVT8Xy5C+mx0NQOC1+z8EXmVApgSvv3dIvj+S1fS/"]},{"shape":[64],"values":["1a/xPfKGfb5YZrm+QdaAvY2Zq77q+ng9IIOJvir7DD+l9Ry+wx8JvnMORT5jsIk87Bg9PvtOGD6wiim+RJ7mvtP+Ej4ue6a+cqqLPtiVM77qpr6+KLHDPtA5Mj/UsES8IuiTOy1U7b5LCoi8EdIIP0Rfnr1fP/I+feEhP2kkQT/+fze+mp7UPCxQOj/EBBW/3NROPmZ/Cj8dQR+7EAbXvmcH3bw+YIQ+ZmIpvlONeTtILYk9RU30PRw0XT5zUos98rRTvEZIkbwPV4m+eKDHPm0jBb9wuIe8RvILvgOEoD4o6gK/4uIqPgAVzTyVlbU9WL5qO/p/K700fFW+Ihe9Pg=="]},{"shape":[64],"values":["Br0SPxxDG7/bWIa/qmevvR6+8r6gWJg+6mbDvi0HJj/i6IG/6iqWvoqYKD8tuf0+9d2JP0qbID+gAbe+1nAyv8jCVD4D/eC+/5+FP7Ctd7+F/AS/jz7jPq4EZj80ONm8WJbaPaQwEb+fwte+hDR6P7lOD79WlMs/M87CP05EhD9IzAS/256+PnjRtD/SVoO/OJGbPlzimT+oKDi+8v4Iv6BvF79+Qak/USPVv7c3qz5Ig7c9zNu2P5p/gT4izgM/F9QnvXgw3b7RKge/rEWEP0iPF7+SlCW/WLvMv6rf3T6TvXG/UrgSP8xR3D3ah1E+mmM3PsFElr8xbaW+NTjKPw=="]},{"shape":[64],"values":["fNaZPmJNkz4VjrU7H3WRvJS+dz2afYa7wM0iPOn6Cz8iOv++/95qPWSaUbkiggY+mmrVPrUdvb0Bnia+svyfOIuHqDfE9k26dl6YvASLsb2wvig+GJYkPlp3Dz+nQC2+NCSEPkV63Twv3La72mk0P8qZtT0sJUQ+rJAuO+fc0bzHR967nbG7PCvwnT6VxAE949oJPPw4rTtt8K29wXVZvRsSvr5Veru+jZXhPLDRVz02wta8vN9sPuhxpb1OFl++3CGLPqPv8T48Fue8Y5XuPd0FSjp7/JI++basvkP1Bj6yar+9832AvdSErL4B5ZA9Yc8Cv3mtZz+7jKg+QgmEuw=="]},{"shape":[64],"values":["htA5Py8Zez8zdPA+RMXVvg6M9D5cvO+9aOsxPHVYPj8yQbu/lEOvP1lhMr94k4o/QMvZP70Edr7f+Cq+4NMeP2ArMzu4gMS90k0hwJ8uTL+ybGs/9p0nP0hDZT/niIK+PHyDP5J44D7W02m/2FilPzDZnj5IT44/lmusOyldmL2Uz2W/MmxMP4/54z9wp5E+d6zXPeBSrDxCB8G90l2zv+iJSb/dBAm/HYnsPuQskT2AVfm+3ml6Pk3ErL4Vfoa+yM2pPgVJtz8s34K+7mWdP1PxKz4jnFQ/pKOZv0hjfz95KYq+GmabvZVPcr/+Uf49qG5gv36t6z92CwQ/LPGCvg=="]}]}],"trainable_params":{"embedding":{"weights":["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"]},"lstm_2":{"weights":["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"],"recurrent_weights":["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"],"bias":["cmwHPhd65j3BR+k9ZNWePne+AD427xU+edWBPqZmgT6g6DE9tfk+vSRAPz5Rxos+IceVPZooLD4IGw0+jRvtPQvWuz2IAAU+muchPtizYLw5c1Q+MiRIPl+LKj61t9M9vbMjPr+wJz46j4I+hdMoPl5ocr203LI97ik/PpGb7zwqORg++uHPPfxh3D0wGOk99ABnPqV6Fj7+Dd89ZlBGPj67iT3uaEs+BmYhPt9ojz6Sjw0+5P4gPinvmj3wqJs+hHiGPnC6ST1b8V0+x1CGPSqNPD6e+Bo+a9wTPtRzCD6qPKI9LsUtPRBLQT0+OEE97OYvPqadED4aIa09HVvKvAZ0kj+krII/2aOBP+twjj/5R2Y/nLiIP1Aaiz9dloI/INeQP2ihjD+piWc/Lld5P1kahj9IBII/SL6GP6kqiT9YOYg/QPqJP9RAdT+7r4U/ZqWFP8fsgD//Fo0/+yeKP5aQZj/tk44/3AKKP53qhj9WXpY/ga6HP3OSez/JDIA/Zo+CPz65fT+XHX8/BMN9PyjoiD/EVpE/s26IPxGQhT9/pos/bp+BP1ZKhj9bpIM/9styP5fojT8sjnk/skF3P7Y3dz+5io0/Rr2QP7nWkD+CTIg/JRSEPz5MbD9smYI/QH6JP7keiz9S040/kAmKP1TSZz8ZHoM/5ziRP3pwjz+aMWi910JZPWN3M7155KY9+xQ3vZlbrj1WWU68S7P2PXco6LxxHwa8wdR1PWoA1L1gro696x6VvCqrnz34RGW91EvcPFAxTzzjJoG9KlCuPFYzAL3pv2u7N1XVvYLatLyAVVw9Yu27PZ2Lhb1XPQg9c1pLveyK6bzcm5C9i2ANvIMZizxB31C9eCLaPVghUz33TK49pw5kvEGQTL3PErI8XOfvPSo4Sr11Sq+8prjsvEMUED1ZWzq9v2k3varB47x+zWa9FUj0POxtdj2wZQS9/6zDPQMqXz3g2KS9KmuzPW3YDjsDMNw9Mj45Pe6t07s0NTU9QMoJvcNuFb2p3bU9","fIQkPtwWCz6cqXQ9OfiFPYi4hj1R8Zk9VKeHPYf/Xj64GQM8vQG7PdQlHT5AJqc+rEIqPt9eij6QcZU9KIPAPRcEYD2ONnM9OpzhPeskFz5AwD68gq0APltgbj3zuJM95B03Piy33rzo97g86LFpPagZXj7QF4M9nMY3PoQzcT3f3vM9Q3ZAPrO2XD6KjIW8tuwGPtUebD6msX8+MKOOPmQOULzDHkQ79hbjPYuLjz56hG8+SXKjvO+y8z2AVe49mMmvPhE3Ir3Yuns9AEKDPF4/gb3LEDA+/v3EPXqMTD2uN5G8lyhrPpW6QD1iEwA8flOGPQbZGT7+mCi9SM+NPQ=="]},"lstm_3":{"weights":["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"],"recurrent_weights":["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"],"bias":["zjgCPh4N5D0jHus9h/d3PQ4EUz73OSY+vXiiPQoJjT151jg+bqsiPrNHaT1Ulgy95CGkPI1jCj4N8v89wBa0PYE4wj2k4U68BdQAPvJvFz3zwi0++f6uPURkGD6aTCI+Be7BPeGjmT4WZB09DiAoPhTnTTySCVY9TuvcPfg06D3ulA4+YfI5PtEDWz4EUTA9qIydPQjmiT04wio+boCDPh8P3D0z/So980C7Paxvuj2YDv478QVIPrnn5T16FZQ+rlRKPnjbFD45ohA+ywOMPfoJbD6O0p49UyfOPQkSaD5yEoM+MWDOPpEKkT2KwCc+fRYmPgrUFz4IDlo9GY6jPT50dT/86IU/dIuDP39YcT/KHm0/2+lxP04FiT9SvH0/bl+FPwVXez/PioA/4keGP3xfhz95+H4/uld8Pztvhz/1P14/XlmCP6gAij99F4A/nHZ6P/xvfD/UZoU/ElZyP2uidj+BgXs/xw2FP7IfgD8u3nw/JE94P4GXgz+VPXA/DGGIP8h2ij83Mnc/GZuAP5IedT8RPH8/EO50P93ufT8cyGs/AoZ6P9YAhD8k/3M/KSx5P77OiD9GSYE/jMiAP/oiZz8wmH0/4Wp4P/N3ez+vC4E/0YGHPwWahD88cnQ/s4BxP/sthj/PxIY/aX2DP1hmgT9aDH4//wJ+P562gD8jA0I95yEpva+Eqz1rMae8p6NqvUHDSr1mwsg9VhASPcHllLwZztU993GHPR2MGjzfGHM5cPmju5S7lj3LdSS9AQ2bvGWjDDxa+dg87NnyvKC8Lz1IdZ28HNmCvTUil7wVeak8VOAZPOaj1zw786E8ZSkrvJkhsr3Bghw8GDKwOGQhaT0jS1A9sUKHvX4CHb3Fmbm9Ivk5PYLVAr2EiMm8OAXxPBA1Oj3ohe+87o9rveFW+rz5+dU8Ch4yPc5RaT0VO/+8mZwePQB7ADUfWca8gGT/vMFMNr0jWh+9wNmJPTaTQzwGCLa8RGdoPUWo/zwNOyc9jCKoPCnzCDx5vBa7","8GHLPWduID5/qD8+NQEjO+6Wcz0NsyM+Dx0VPkuTFj733dE9ifdWPTPyTzyy4gg+RKePvLsPPT4Hf1A+akqePUaHJT63aK49F+ATO5UkMT6ToTc8PT9wPtAitD0s0g4+yKFkPZi8RD7O0U89UgxmPN5uCT5sYd08vMWnPa0kPT73FSq8UBGOPfZjmT6+UUo9nDqkPfjKeT0W7AM+mHDMPcxODD4zeKM88WQEPoWgzDuiJug9zyQSPm50izv9qP09p01kPgKKpz3Ltyk+Rt9+PTI3FL1u0to9XEJnPsghmj1vEI0+k1rtPcnkOD7b9lM9EttBPs6mFj5NfNE8KACvOw=="]},"dense_1":{"weights":["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"],"bias":["TSIgv33GDj1j/IA9Lc8YPmQ51Duf4p09F4Q7Pb3/5rtg5c29d7XnPIHT+DuB5V89E4wpvzF37L5/cSG/BXUHwEYTkT0u3+S8cF3Wu5yrnT3bKTS+8CPBvsIrC70pm5S+X+zMvYwa9r6He8683aUhvvkMfL4wtw2/eGbIv2BNdL4cyVy+9pVMO427mL/21xG9pqq9PfTzHzwaTfi8SKsiPP+COb+Lgho8bKqUPTj8Fb4wqGO9cUaAvGhJerzhMT88DOenviPQBD7x1iS+SauIPevUn7o1vr28KvKmvm1CIL4MNVS+mvCKvkFLiTwcfTi/"]}},"hash":"327c7ac77e5ebfd32c6206dc9867da45306db90247ebfa0f82b9a0da262db63a"} \ No newline at end of file diff --git a/src/kernels/gfx942_ConvHipIgemmGroupXdlops_encoder.ktn.model b/src/kernels/gfx942_ConvHipIgemmGroupXdlops_encoder.ktn.model new file mode 100644 index 0000000000..77c2ef1f93 --- /dev/null +++ b/src/kernels/gfx942_ConvHipIgemmGroupXdlops_encoder.ktn.model @@ -0,0 +1 @@ +{"architecture":{"class_name":"Functional","config":{"name":"model","trainable":true,"layers":[{"module":"keras.layers","class_name":"InputLayer","config":{"batch_input_shape":[null,18,18],"dtype":"float32","sparse":false,"ragged":false,"name":"input_1"},"registered_name":null,"name":"input_1","inbound_nodes":[]},{"module":"keras.layers","class_name":"Dense","config":{"name":"dense","trainable":true,"dtype":"float32","units":64,"activation":"linear","use_bias":false,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"kernel_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"bias_constraint":null},"registered_name":null,"build_config":{"input_shape":[null,18,18]},"name":"dense","inbound_nodes":[[["input_1",0,0,{}]]]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm","trainable":true,"dtype":"float32","return_sequences":true,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[null,18,64]},"name":"lstm","inbound_nodes":[[["dense",0,0,{}]]]},{"module":"keras.layers","class_name":"LSTM","config":{"name":"lstm_1","trainable":true,"dtype":"float32","return_sequences":false,"return_state":true,"go_backwards":false,"stateful":false,"unroll":false,"time_major":false,"units":64,"activation":"tanh","recurrent_activation":"sigmoid","use_bias":true,"kernel_initializer":{"module":"keras.initializers","class_name":"GlorotUniform","config":{"seed":null},"registered_name":null},"recurrent_initializer":{"module":"keras.initializers","class_name":"Orthogonal","config":{"gain":1.0,"seed":null},"registered_name":null},"bias_initializer":{"module":"keras.initializers","class_name":"Zeros","config":{},"registered_name":null},"unit_forget_bias":true,"kernel_regularizer":null,"recurrent_regularizer":null,"bias_regularizer":null,"activity_regularizer":null,"kernel_constraint":null,"recurrent_constraint":null,"bias_constraint":null,"dropout":0.0,"recurrent_dropout":0.0,"implementation":2},"registered_name":null,"build_config":{"input_shape":[null,18,64]},"name":"lstm_1","inbound_nodes":[[["lstm",0,0,{}]]]}],"input_layers":[["input_1",0,0]],"output_layers":[["lstm",0,1],["lstm",0,2],["lstm_1",0,1],["lstm_1",0,2]]},"keras_version":"2.16.0","backend":"tensorflow"},"image_data_format":"channels_last","input_shapes":[[18,18]],"output_shapes":[[64],[64],[64],[64]],"tests":[{"inputs":[{"shape":[18,18],"values":["0yNTwKJL1b0la2s/jcv4PEpHGT9GuS+/4he1P2a9Gj40sjC/wYEmPhGjJr+NIz+/vVwnPslDQr/XNZe/oP9tPlxGgD87GZw/FHofP5AA/T4Df/q+N2COP8R69D8rsWA/49CvP/EjIz/vK/y/i2vkv3NqBj5v7XC/teb1vndBA79lJZg+3UL7vZlFfr+lDwTAJMtYPS6eFMBx1Ku/a4nzv2Ckmr+pQkW/kePQPt+dcr5fPS+9S+3wvvNeXT7BtwE/rEQcwE+dF7+RHXe+RQwmv7bJm7+O882/EpRRv8xwfr2sAAI+yDizPpApMj/yPbo+YZ4iv8qrnD/fbZU++F0UP983lr8yUac+GX/CPmFI/r5lPlM/zSOwviIEkb/+XZI///7QvrCwqz7jyIA/Pve3P1WxXb9aEFa+51SrPuj5sL8ApZm/SEYaQF82sL2sISNA8x8qPw0+KcD2LK4/zsYNvsX6kT9Wduo/01aPPl7COj87hQZAwJD0Pbpz5T5VXT4/SCN3v8n8oD6iZau/7HhVvyduiz0Mm4w9gJd+v8kKPz+3IoQ9E7smv+ac0z6KFwS/gQrkvwhDAEC9U9e+4JeiPgsyqz+qfmS/R7FEv8MGmz+ATgu/8H9hPzZmaD+O1Wk/+SnvPnGgLz4Sbme/i+zDvaGlpL/4E4M+nweOv2SU2L1Jbbi/FfJUvzXMer+xaYI/VHA7P8kJBsDRQpq+gdqNvkJA1T4g2Ow+L2Ulv7q56r/of5U/iPR2v+/Znb/VH+M9XJ7APfW9dT/0zE2/fqlDv+I0Aj/diAQ/F4cBvu54AcCMsug93c4wPyfnnz8d8LO/SGjJPkpuhT/9KJI/HVr0vonbeT+eRn0+uqAVP4isw79idUc/o8gAvswcTr6huLQ/OZRrP+bYHj+nX52+fdiEvxPcfz1FmzO89JjHv/fyZz9n4tA/5Y6rPqzBNr5pvRK/CEUPPlv5QD9w6rC/xIkNQDasir42bpc/kUmTvcGqAT8DVi2/Iw/OP5LjfT/CYL6/","AuIvv/bHjz4dGLO+Bdmav6nxuD90wpk/uttxPiOYxr/aslU9Bcb1PgVl0D+62zY/wR6Lv60KEL5oQ50/dLy9PoAP377tTsW+IbChPt0LpT65Ism/QE5Gv4UGmT+CJVI/zJhcP7Yjn72nfnE/2O5Rv2WrIj9nMls/u6aav6S1PsDGgEm/0xDnvp8DCT+T1CG/TFk8vgMjTr/5mRq/0O/UvXwEIb9PsVa/mXSWvzdhIj7Zg9K/lgxxv9xHo79KvQ8/V0HsPvUOBkCE4lY+oYRsP++Por9w+1O/efEpPx7MlD/8OT4+JKu2P71DvL94jt+8nyEuQL0muT+7v/u/lhVZPz2k0j5At0s/EUz9vskPpb5di4u/EPaVv1p1NcDp9a4+vubGP4PKgb9qghk/MMumv6BO2T5R6QPAfhDxP13lDD5pOhG+TF67PnJA6D+BOJ6/uchHvCEFPj0glvA//5XXvvqMvr4LZYs/3ixbvg7nE78+XWS/Dq25v0KB/L6vlxtAEIrJv1+oKz5DYR2/tnDwvnSRtb8wGk4/+O/dP1x8bT+9woo+KLbdPWiBg79aoMa/yAiQP1Nw4D/76ks9nGesPxXo4r5g1QG/czSWvTILvT4gg6m+SGU8P+MF2z+J7R+/hhxAvwVxhD8m1wE/VpSXvp5ztz5eLTM+ezpDv1mvEz8PQQi/tOTlv3iFOj/n1J6+"]}],"outputs":[{"shape":[64],"values":["13oxP8iSvbz+17e+MaZNP9Oqy72v2N29KR1Tv29qOr4CsdQ88Z9IP71CYT4n6f0+rk8Vvy4VAD8wIS+/dKQGP6UBJL/CL3S/V9LzPVEAgL5Mo0s+KMZYvnlmhT6veO09+DcnPg/0XT/gAVo+Jfg1P5GvmL4/wlW8lKDaPlOqgz7Fck4+6GTPvdAQRD0UnXY/HvofPtwLib5yGBW/2kxgPlb8jL5jxQk/9MeJPv8lfz7utY6+DkBhP9AbNL/aLO0+EgGDPaK8hb7E624/ESCdvldZgr56fEa/tOX1PfmIzr4U/sA8NM4gPt4kXj9ZAhi/j0laPVQiu7tAzR8+LFCgPg=="]},{"shape":[64],"values":["Fv2ZP1qiPr3BTH6/Q9QoQErU8b0+Osy/TgtnwLNBUb749Rg+nQK4P0oSrD4TSZw/9bPjv3+XJT/crgbAR1OAPw6swL8lTP+/4JPDP3LmJr8O20c/WaWjvhSOnj5uQEY/pLNkPrWQO0C7gwU//jGjP856Rb9B/Lu+/nc2P3wNjD7uOD4/Ar3wvbvOUD5ofR1AgCk9Psi0Gb+K8F6/kCqXPsiDbr+SQOY/CqKhPvJulj4Qc4K/uBgJQJreZb9+fwc/0C0QPq7uvb6SAWZAMglNvyefsL4XTJa/5oQMPgUgCb9zDiI+cdV0P9z9qz+/r5u/6DgWPkTZv715cFE+oy3qPg=="]},{"shape":[64],"values":["j4l0Pnhx+j14ens+ByEQvz01s70VNp+9E9fBvpp34jxbXMK+YpPhPrW2BD1gEuU+bbdkPdqH6r66f0Q+b5povvrAxDwbaNM+6JU/vp8KxT6xZaA+fBqCvp9auT4LogC/n2RDPqIFTb4bsCc+WdXRvg9PlL2VKhQ/XyOYvMgHv77YW+0+nzTHPuGcQj/YhCK+9DbkvDsFF7+F6vs+f4M5vgLoAj82fwS9dHAHvvEf7j4glE6+FBpVPn8AFb4Ddp2+8rjiPJseBT60bzi9mW8gP14bID7Bf0k+KPCpPfcokD1C2EM+y9yXPpLjr7w3Ct49EIrxvYpOAT4vK6s+FijpvA=="]},{"shape":[64],"values":["qA7SPsp2pz5svbc+LebQvxCGvb+mJTm/CrWEvwZVLj3U2Xu/QjMYQKI6uj7ikY0/2u7XPgJDvb+zLCA/kZUKvxlwFT0SRG0/go7Av5ArEz/4phg/9gEJv47yMD889Gy/dKyGP9E+B7/gs0w+ehfHvzic0b1ahcQ/AyiIvqcslb+n884/1mEeP5rJzT+/lQy/Z1hEvki/h7/VaCFAH0ppv3EriD9eMBm9WZ1GvqGxQj9eTaa+YGunPq+VUb4x67K+KxYEPeuYvz5XFjW/RhrgPzCoaj/qCrk+X2+APm/K+T0qSzE/FfAKPxAhSL08t7k+eoWXv19PKT8x8Qc/f/3cvQ=="]}]}],"trainable_params":{"dense":{"weights":["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"]},"lstm":{"weights":["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"],"recurrent_weights":["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"],"bias":["V0BdPgGNmb+ON1w9WHZevEUJmz5JafQ+gvrKPzuQArzP6fi9c0EJP1bXdj4qMcw+p2QCQGXjGT5C68c+qAgAPXhCCj99C4Y/WUunPtMIvrrf3+8+QB2DPaOIED8EwhJAqeYmPWunST5J+48/6PO4P60c/j2ioek+4ZGxPgPL7z76eJA8qZ+aPQAMAL2Pz789abj0POollT5S+ZA+oSPNPYOpHT+2p/g9HPKwPn45fD4cxIm9pf4SP46xbT1ZzLM+yod6Pks/WD9GkMI/051yP/yxRD5+cw4+zob/PYfA6z1jT7292aFqvOHCGD/142w/g9LLPYD4/j2fbn4+KdfovW1Jhj8ppJE/wOqMPzWBkz8j03w/yu6NPybijD+e/4s/tcCFP5O9gT8u/qE/BE+FP7Z4kD/mhow/SuKsPyA/iT8KxZ8/vgCLPyTQgz/JkqE/+5uJPyvkpz/lj6k/Ww+CP1nThz/Rsp0/BqaFP1WijD/4tow/7CmEP96EZz9OeIY/mol/P4wviT+gH4Y/B9OkP075iT9juZY/ZriBP63/iD8vxXU/ivZ8P10Ziz/enZI/VFOQP7DKhz9XIJA/kgh1P+TFjT+vEo8/RROjP2GjnT+EWYw/1UmCP/HUij+IEYM/8uyrPw5apj9TKog/7NlwPzShmD+jaIw/fYykP9xfkT8kbR0+wOCdPfMJbz0ISCO9S/s8PlPib7+sdhrA724LPJXMKz0T1wk/inHau+Vrej0FwJG/WTM0vZX4wb70DRO8R9xyvtRQsb72I0M9VYcjPWRdaj7dBa88H3v8PkUGlD/UAgE9BCp0PjkQBz8D/jw/VEA+Pq4ijjy3/BA9apyxvQdFaz2v7I0997mJPj0bKj5n+5I9QX0qvjt+fD2+rZW7utOUPcU3fj0TEsE9XbbbO4aIXL0gOGY/hG1SvSqNrT2hcrC8CcxWvyFO2D88RTw/UlAMvCDvyr1jq6C8woVJPfbosT4ONpm7ax2CPQC0lj71pxI9rgw3PgGjFz7ERnG+","yiYkPasFLz6EaoE+0vXbu2DxvDxbRAU9bExJPjwBJz1qSM084LGDvVTPDz6fZ9c9uYWNPkENTrw1M8g9k2R8PfkVwr1rOQI+buZzPXQOgz6AL029shATPjoPJj4ppZo+yZ2mPWpl6L3O4rg+b0BSPukRNb0bwe48JeZ+Peb3Cj5zCgC9Y/WAO7J9KT13z4q9L60PPyM5pz7bJ8I+X0IDPpTCTj6CvHo+P//wPRyPsj2U2Kw+vvIePutsFbwLviY+bjIjPsw31D2N96g+K7x8PL8Kaj20iSE+rpgBPeDCmD5SI8w92KMWPt/cyD1NtUY8843wPIkq6L0rEJI9+sacPg=="]},"lstm_1":{"weights":["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"],"recurrent_weights":["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"],"bias":["JgUsPcMXDj5J4dk9XyIFPtNblj2Hi1U+9kczPUxTQbsrsmM9Xj8gPtHAwj1Vgka8mm5IPXZ9Oj4pOuI9jBoAPcK3NT0NDxE+P/K5PQr8ZD7tf5499DJQPVxTVLu/0/899FEjPtqAQD7BqZw9UhmaPYPOBj1lWmU+kqCOPJgb7z08Djw9ikriPSKLVz7p0dw9Iet8PXKblD0xozk+bN3wPeNDCT5kBi27N+LtPYRCiD3o7/w8SomEPBKtvj145zA9z8EIPifPzz2BGgE9BP1gPm8mxD2yxY49v60oPn5RUTykW0g+qAHRu+5r7D0BBXE9m57IPY10wT3/4Ac+Mr/bPfkriT/vgo4/mu2HPwzbjz9Awow/8cuOP8r+hT88GIs/4BaKP8q0gz+BlYg/46eDP6MrgD84xpA/TTJ/PySzgj9y+YM/wS9+P6IciT8P24o/ba6NP1nzgT+/jII/Q72LP2Pnlz9t/IY/kxiUP+I2fD+u+Io/FueSP8RkgT/GSIo//tSLP8uXhj9eXo4/EbONP14mkD9rYIM/+wONP41Ehz+G94Q/MtWCP4taij+JM4U/B1uNP23SiT/vXYs/BleAP2sViD9MGIw/tXWCP/6ElT+rGoY/jcKNP8T3iT+ONqA/naCKP4EjfT/Q44w/HRSPP/jshD9SkIg/yciDPwb1hj+V+h+9yh4WPb08xr1aJL29KHyjvfbSQb3B5+y8BrbAvOqZRb1G6cM9fcHdvTeNyj2gFfK8uQl7u0reWT2n1/27Ab12vNcxrjt2jRs89bMdPmVKQD1uRXm98GNyPGGwXb3qo5Y91gYFPXyZbb3oYuK89U6PPClLaT5LMNe8AWlzPSelZj1hyoA9uTTpO6IC7z1rJVA9H9d0PWWO8z1f8HK8QgBYPLAwFbuggZ68g8+jPDXmkzy5PBk8LxurvYB83zy0SnY8aNqVPCSDA71O8Cs+D0qIvHDfPLwRB5e8KnOzPfAGJ70STNQ8LIemvX2NID0y5629JaSzPCD/4zp6oQm+","7OWUPLQOObybtTq9zkqXO8lwzTuG66y8y7WDO6/0Rb1wVd28UuemvbhQ/Lxju0K70qoJPEEG5j2/gf+8oZpKvRJzTr1BWqg7aJGxPSaWsTuMx0g9XSNivbyzL718/oO87M/MPHOfwLthHES94tA4vPQfH70Jtqo8ZOm4PPYTLLxUPRK9gziCPV1PZztO9Iq8c91kPdXtZj2kxcC8YCIWPXOwCzwImJI8pd+lvN6Qnry1mz69MrXpvJwCELxijam7kB5/vQpbw7wAnBI95GbJPPZwZ7yqs4E7MNTPPZ/tQL3rrDw8EgLPvBv5AL1YY0a9yOA1O+xA171jLMw9czFovQ=="]}},"hash":"15f24454143bfeb1a8c3303787d4f8183e4f70bdbfd439317f2a25e95648ba4c"} \ No newline at end of file diff --git a/src/kernels/gfx942_ConvHipIgemmGroupXdlops_metadata.ktn.model b/src/kernels/gfx942_ConvHipIgemmGroupXdlops_metadata.ktn.model new file mode 100644 index 0000000000..f4ce5ffbd0 --- /dev/null +++ b/src/kernels/gfx942_ConvHipIgemmGroupXdlops_metadata.ktn.model @@ -0,0 +1,71 @@ +{ + "num_tuning_params": { + "fwd": 13, + "bwd": 14, + "wrw": 14 + }, + "decodings": { + "tunings": { + "0": "0", + "1": "256", + "2": "64", + "3": "128", + "4": "64", + "5": "128", + "6": "32", + "7": "256", + "8": "128", + "9": "64", + "10": "32", + "11": "256", + "12": "32", + "13": "4", + "14": "16", + "15": "8", + "16": "Default", + "17": "Filter1x1Stride1Pad0", + "18": "OddC", + "19": "Filter1x1Pad0", + "20": "8", + "21": "4", + "22": "2", + "23": "32", + "24": "Default", + "25": "2", + "26": "Filter1x1Stride1Pad0", + "27": "1", + "28": "4", + "29": "32", + "30": "32", + "31": "1", + "32": "2", + "33": "4", + "34": "32", + "35": "8", + "36": "4", + "37": "1", + "38": "2", + "39": "1", + "40": "2", + "41": "4", + "42": "8", + "43": "2", + "44": "1", + "45": "8", + "46": "4", + "47": "4", + "48": "2", + "49": "1", + "50": "8", + "51": "4", + "52": "1", + "53": "8", + "54": "1", + "55": "8", + "56": "4", + "57": "2", + "58": "-1", + "59": "-1" + } + } +} \ No newline at end of file diff --git a/src/kernels/gfx942_metadata.tn.model b/src/kernels/gfx942_metadata.tn.model new file mode 100644 index 0000000000..63f5f25b0c --- /dev/null +++ b/src/kernels/gfx942_metadata.tn.model @@ -0,0 +1,473 @@ +{ + "generated_on": "14 Mar 2024, 23:38:55", + "database": "tuna_net2", + "gpu": { + "arch": "gfx942", + "num_cu": "304" + }, + "golden_v": null, + "kernels": "hip", + "num_inputs": 18, + "num_solvers": 20, + "num_outputs": 20, + "encodings": { + "Direction": { + "B": 0, + "W": 1, + "F": 2 + }, + "Precision": { + "FP32": 0, + "FP16": 1, + "BF16": 2 + }, + "Layout": { + "NCHW": 0 + }, + "solver": { + "ConvAsmImplicitGemmGTCDynamicBwdXdlopsNHWC": 0, + "ConvAsmImplicitGemmGTCDynamicFwdXdlopsNHWC": 1, + "ConvAsmImplicitGemmGTCDynamicWrwXdlopsNHWC": 2, + "ConvBinWinogradRxSf2x3": 3, + "ConvBinWinogradRxSf2x3g1": 4, + "ConvBinWinogradRxSf3x2": 5, + "ConvDirectNaiveConvBwd": 6, + "ConvDirectNaiveConvFwd": 7, + "ConvDirectNaiveConvWrw": 8, + "ConvHipImplicitGemmGroupBwdXdlops": 9, + "ConvHipImplicitGemmGroupWrwXdlops": 10, + "GemmBwd1x1_stride1": 11, + "GemmBwd1x1_stride2": 12, + "GemmBwdRest": 13, + "GemmFwd1x1_0_1": 14, + "GemmFwd1x1_0_2": 15, + "GemmFwdRest": 16, + "GemmWrw1x1_stride1": 17, + "GemmWrwUniversal": 18, + "fft": 19 + } + }, + "stats": { + "overall": { + "features": { + "mean": { + "Inp_0": 326.03692626953125, + "Inp_2": 209.5792236328125, + "Inp_3": 222.9921417236328, + "Out_0": 305.2900085449219, + "Out_2": 148.55433654785156, + "Out_3": 158.05218505859375, + "Fil_1": 2.187760353088379, + "Fil_2": 2.1930642127990723, + "Pad_1": 0.5850193500518799, + "Pad_2": 0.5841475129127502, + "Str_1": 1.3526216745376587, + "Str_2": 1.3526238203048706, + "Dil_1": 1.010040044784546, + "Dil_2": 1.010040044784546, + "BatchSize": 21.28179359436035, + "Precision": 0.1066257655620575, + "Direction": 1.0000214576721191, + "GroupSize": 1.4371129274368286 + }, + "std": { + "Inp_0": 395.2181701660156, + "Inp_2": 247.88763427734375, + "Inp_3": 262.0624084472656, + "Out_0": 388.76434326171875, + "Out_2": 131.54954528808594, + "Out_3": 139.3096160888672, + "Fil_1": 1.8566700220108032, + "Fil_2": 1.8882192373275757, + "Pad_1": 1.0520880222320557, + "Pad_2": 1.0513699054718018, + "Str_1": 0.48334959149360657, + "Str_2": 0.4833502471446991, + "Dil_1": 0.5055450797080994, + "Dil_2": 0.5055450797080994, + "BatchSize": 99.88484191894531, + "Precision": 0.3966391384601593, + "Direction": 0.8166793584823608, + "GroupSize": 9.6254301071167 + } + }, + "gt": { + "mean": { + "p0": 0.039825189858675, + "p1": 0.09313251078128815, + "p2": 0.029506459832191467, + "p3": 0.004609859548509121, + "p4": 0.07422647625207901, + "p5": 0.09191850572824478, + "p6": 0.0014207150088623166, + "p7": 0.004581042565405369, + "p8": 0.0013409176608547568, + "p9": 0.1970977634191513, + "p10": 0.2844267189502716, + "p11": 0.03611767664551735, + "p12": 0.0061945426277816296, + "p13": 0.008105386048555374, + "p14": 0.07681375741958618, + "p15": 0.017690090462565422, + "p16": 0.02428811974823475, + "p17": 0.004555359948426485, + "p18": 0.004065678454935551, + "p19": 8.331891876878217e-05, + "t0": -0.45725664496421814, + "t1": -0.5293848514556885, + "t2": -0.5532383918762207, + "t3": 0.05000653490424156, + "t4": 1.4272518157958984, + "t5": 2.0281379222869873, + "t6": 112.77667999267578, + "t7": 4.223079681396484, + "t8": 26.47581672668457, + "t9": -0.6579961776733398, + "t10": -0.6738874316215515, + "t11": 0.6840386390686035, + "t12": -0.9428364038467407, + "t13": -0.6565430164337158, + "t14": -0.8062157034873962, + "t15": -0.9367114901542664, + "t16": -0.595338761806488, + "t17": 0.8091187477111816, + "t18": 3.589270830154419, + "t19": -0.9993996024131775 + }, + "std": { + "p0": 0.0785060003399849, + "p1": 0.14597192406654358, + "p2": 0.09589290618896484, + "p3": 0.023409124463796616, + "p4": 0.09029544144868851, + "p5": 0.09431690722703934, + "p6": 0.01625385321676731, + "p7": 0.021194985136389732, + "p8": 0.015146051533520222, + "p9": 0.3000956177711487, + "p10": 0.41848132014274597, + "p11": 0.09205522388219833, + "p12": 0.03242010995745659, + "p13": 0.028519151732325554, + "p14": 0.17827166616916656, + "p15": 0.08681554347276688, + "p16": 0.0695795863866806, + "p17": 0.031367965042591095, + "p18": 0.019014379009604454, + "p19": 0.004712354391813278, + "t0": 2.946953296661377, + "t1": 1.4707006216049194, + "t2": 1.1833020448684692, + "t3": 4.1849470138549805, + "t4": 12.375580787658691, + "t5": 10.55478286743164, + "t6": 798.5470581054688, + "t7": 30.990955352783203, + "t8": 248.3822479248047, + "t9": 0.5040930509567261, + "t10": 0.4734415113925934, + "t11": 164.3551788330078, + "t12": 0.3171423673629761, + "t13": 12.054414749145508, + "t14": 5.930906772613525, + "t15": 5.913235187530518, + "t16": 5.812395095825195, + "t17": 19.083234786987305, + "t18": 69.54570007324219, + "t19": 0.02794887311756611 + } + } + }, + "train": { + "features": { + "mean": { + "Inp_0": 326.0245361328125, + "Inp_2": 209.55429077148438, + "Inp_3": 222.93283081054688, + "Out_0": 305.1783447265625, + "Out_2": 148.56773376464844, + "Out_3": 158.04930114746094, + "Fil_1": 2.18635892868042, + "Fil_2": 2.192021369934082, + "Pad_1": 0.5846491456031799, + "Pad_2": 0.583894670009613, + "Str_1": 1.3525774478912354, + "Str_2": 1.3525798320770264, + "Dil_1": 1.0103939771652222, + "Dil_2": 1.0103939771652222, + "BatchSize": 21.191417694091797, + "Precision": 0.10652011632919312, + "Direction": 1.0005378723144531, + "GroupSize": 1.4348441362380981 + }, + "std": { + "Inp_0": 395.21356201171875, + "Inp_2": 247.89434814453125, + "Inp_3": 261.6176452636719, + "Out_0": 388.3612060546875, + "Out_2": 131.58705139160156, + "Out_3": 139.13621520996094, + "Fil_1": 1.855620265007019, + "Fil_2": 1.8879785537719727, + "Pad_1": 1.0555751323699951, + "Pad_2": 1.0549452304840088, + "Str_1": 0.48315295577049255, + "Str_2": 0.48315367102622986, + "Dil_1": 0.5134463310241699, + "Dil_2": 0.5134463310241699, + "BatchSize": 99.39671325683594, + "Precision": 0.3963705003261566, + "Direction": 0.8164721131324768, + "GroupSize": 9.604955673217773 + } + }, + "gt": { + "mean": { + "p0": 0.039810314774513245, + "p1": 0.09311690181493759, + "p2": 0.02952294796705246, + "p3": 0.0046117291785776615, + "p4": 0.07422029227018356, + "p5": 0.0919346958398819, + "p6": 0.0014079868560656905, + "p7": 0.004585471004247665, + "p8": 0.0013485761592164636, + "p9": 0.19678474962711334, + "p10": 0.284714937210083, + "p11": 0.0361124686896801, + "p12": 0.006202039308845997, + "p13": 0.0080783162266016, + "p14": 0.07680630683898926, + "p15": 0.01772368885576725, + "p16": 0.024297762662172318, + "p17": 0.004563942551612854, + "p18": 0.004070402123034, + "p19": 8.647642243886366e-05, + "t0": -0.4601359963417053, + "t1": -0.529330849647522, + "t2": -0.5524445176124573, + "t3": 0.04988675191998482, + "t4": 1.4290804862976074, + "t5": 2.0291473865509033, + "t6": 112.41471099853516, + "t7": 4.2160325050354, + "t8": 26.491165161132812, + "t9": -0.6583772897720337, + "t10": -0.6735464334487915, + "t11": 0.6356092095375061, + "t12": -0.9427528381347656, + "t13": -0.6546080112457275, + "t14": -0.8051235675811768, + "t15": -0.9356460571289062, + "t16": -0.5958107709884644, + "t17": 0.8143430352210999, + "t18": 3.592458963394165, + "t19": -0.999393880367279 + }, + "std": { + "p0": 0.0786193460226059, + "p1": 0.14594127237796783, + "p2": 0.09583792835474014, + "p3": 0.02338370308279991, + "p4": 0.09028605371713638, + "p5": 0.0943465530872345, + "p6": 0.015997454524040222, + "p7": 0.02124175615608692, + "p8": 0.015236618928611279, + "p9": 0.2998940050601959, + "p10": 0.4185950458049774, + "p11": 0.09207578003406525, + "p12": 0.03242572024464607, + "p13": 0.028367508202791214, + "p14": 0.17825227975845337, + "p15": 0.08692070096731186, + "p16": 0.06958458572626114, + "p17": 0.031421445310115814, + "p18": 0.01897948421537876, + "p19": 0.004851889796555042, + "t0": 2.7519032955169678, + "t1": 1.4803502559661865, + "t2": 1.2048041820526123, + "t3": 4.168496131896973, + "t4": 12.817203521728516, + "t5": 10.721745491027832, + "t6": 803.9323120117188, + "t7": 31.02450180053711, + "t8": 251.00262451171875, + "t9": 0.504469633102417, + "t10": 0.47357308864593506, + "t11": 160.39544677734375, + "t12": 0.3180963099002838, + "t13": 12.6846923828125, + "t14": 6.2501301765441895, + "t15": 6.232358932495117, + "t16": 5.88792085647583, + "t17": 19.83079719543457, + "t18": 70.37797546386719, + "t19": 0.028129665181040764 + } + } + }, + "test": { + "features": { + "mean": { + "Inp_0": 326.14862060546875, + "Inp_2": 209.8037567138672, + "Inp_3": 223.5259552001953, + "Out_0": 306.2955322265625, + "Out_2": 148.4342041015625, + "Out_3": 158.07801818847656, + "Fil_1": 2.2003726959228516, + "Fil_2": 2.2024505138397217, + "Pad_1": 0.5883512496948242, + "Pad_2": 0.5864233374595642, + "Str_1": 1.3530193567276, + "Str_2": 1.3530193567276, + "Dil_1": 1.006854772567749, + "Dil_2": 1.006854772567749, + "BatchSize": 22.095172882080078, + "Precision": 0.1075766310095787, + "Direction": 0.9953730702400208, + "GroupSize": 1.4575327634811401 + }, + "std": { + "Inp_0": 395.2637939453125, + "Inp_2": 247.8297119140625, + "Inp_3": 266.0340270996094, + "Out_0": 392.37628173828125, + "Out_2": 131.21286010742188, + "Out_3": 140.8621368408203, + "Fil_1": 1.8660633563995361, + "Fil_2": 1.8903788328170776, + "Pad_1": 1.020172119140625, + "Pad_2": 1.0186352729797363, + "Str_1": 0.4851207435131073, + "Str_2": 0.4851207435131073, + "Dil_1": 0.427910178899765, + "Dil_2": 0.427910178899765, + "BatchSize": 104.17271423339844, + "Precision": 0.3990517854690552, + "Direction": 0.8185360431671143, + "GroupSize": 9.807867050170898 + } + }, + "gt": { + "mean": { + "p0": 0.039959024637937546, + "p1": 0.0932728722691536, + "p2": 0.029358016327023506, + "p3": 0.004593030083924532, + "p4": 0.07428206503391266, + "p5": 0.09177281707525253, + "p6": 0.0015352690825238824, + "p7": 0.004541176371276379, + "p8": 0.0012719921069219708, + "p9": 0.19991455972194672, + "p10": 0.2818325161933899, + "p11": 0.03616458550095558, + "p12": 0.006127072498202324, + "p13": 0.008349020965397358, + "p14": 0.07688078284263611, + "p15": 0.017387699335813522, + "p16": 0.02420131303369999, + "p17": 0.00447811046615243, + "p18": 0.004023166839033365, + "p19": 5.4901607654755935e-05, + "t0": -0.43134167790412903, + "t1": -0.5298715829849243, + "t2": -0.5603829622268677, + "t3": 0.05108462646603584, + "t4": 1.41079580783844, + "t5": 2.0190505981445312, + "t6": 116.03421020507812, + "t7": 4.286505222320557, + "t8": 26.337644577026367, + "t9": -0.6545664072036743, + "t10": -0.6769590973854065, + "t11": 1.1199018955230713, + "t12": -0.9435877799987793, + "t13": -0.6739567518234253, + "t14": -0.816045880317688, + "t15": -0.9462993144989014, + "t16": -0.5910897254943848, + "t17": 0.7621014714241028, + "t18": 3.560584783554077, + "t19": -0.9994518160820007 + }, + "std": { + "p0": 0.07747907191514969, + "p1": 0.14624904096126556, + "p2": 0.09638715535402298, + "p3": 0.02363692969083786, + "p4": 0.09038089960813522, + "p5": 0.0940505862236023, + "p6": 0.01840115897357464, + "p7": 0.02076948620378971, + "p8": 0.014305136166512966, + "p9": 0.3018926978111267, + "p10": 0.417452335357666, + "p11": 0.09187104552984238, + "p12": 0.03236985206604004, + "p13": 0.02984851412475109, + "p14": 0.17844805121421814, + "p15": 0.08586370199918747, + "p16": 0.06953523308038712, + "p17": 0.03088271990418434, + "p18": 0.019325751811265945, + "p19": 0.003192919073626399, + "t0": 4.3229851722717285, + "t1": 1.380836844444275, + "t2": 0.9685168862342834, + "t3": 4.330243110656738, + "t4": 7.281820297241211, + "t5": 8.912556648254395, + "t6": 748.338134765625, + "t7": 30.68766212463379, + "t8": 223.42257690429688, + "t9": 0.5006834268569946, + "t10": 0.47224971652030945, + "t11": 196.43455505371094, + "t12": 0.3084263503551483, + "t13": 2.230865955352783, + "t14": 0.423647940158844, + "t15": 0.2879754900932312, + "t16": 5.08246374130249, + "t17": 10.117057800292969, + "t18": 61.55158996582031, + "t19": 0.026266032829880714 + } + } + } + }, + "conv_params_used_as_features": [ + "Inp_0", + "Inp_2", + "Inp_3", + "Out_0", + "Out_2", + "Out_3", + "Fil_1", + "Fil_2", + "Pad_1", + "Pad_2", + "Str_1", + "Str_2", + "Dil_1", + "Dil_2", + "BatchSize", + "Precision", + "Direction", + "GroupSize" + ], + "redundant_columns": { + "SpatialDim": 2.0, + "Inp_1": 1.0, + "Out_1": 1.0, + "Fil_0": 1.0, + "Pad_0": 0.0, + "Str_0": 1.0, + "Dil_0": 1.0, + "BiasFlag": 0.0, + "Layout": 0.0 + } +} \ No newline at end of file diff --git a/src/kernels/miopen_rocrand.hpp b/src/kernels/miopen_rocrand.hpp new file mode 100644 index 0000000000..eeffbfabca --- /dev/null +++ b/src/kernels/miopen_rocrand.hpp @@ -0,0 +1,77 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include "miopen_limits.hpp" +#include "miopen_cstdint.hpp" + +#ifdef __HIPCC_RTC__ +#define WORKAROUND_IGNORE_ROCRAND_INCLUDES 1 +#else +#define WORKAROUND_IGNORE_ROCRAND_INCLUDES 0 +#endif + +#if WORKAROUND_IGNORE_ROCRAND_INCLUDES == 1 +// disable math.h from rocrand (it conflicts with hiptrc) +// NOLINTNEXTLINE +#define _GLIBCXX_MATH_H 1 +#endif + +// disable normal-distribution shortcuts (it bloats prng state) +#define ROCRAND_DETAIL_XORWOW_BM_NOT_IN_STATE +#include + +namespace prng { + +// based on splitmix64 +inline constexpr uint64_t hash(uint64_t x) +{ + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull; + x = (x ^ (x >> 27)) * 0x94d049bb133111ebull; + x = x ^ (x >> 31); + return x; +}; + +// borrowed from +// rocrand_uniform.h has too many dependencies and device code +inline constexpr float uniform_distribution(unsigned int v) +{ + constexpr float rocrand_2pow32_inv = +#ifdef ROCRAND_2POW32_INV + ROCRAND_2POW32_INV; +#else + 2.3283064e-10f; +#endif + + return rocrand_2pow32_inv + (static_cast(v) * rocrand_2pow32_inv); +} + +inline constexpr float xorwow_uniform(rocrand_device::xorwow_engine* state) +{ + return uniform_distribution(state->next()); +} + +} // namespace prng diff --git a/src/load_file.cpp b/src/load_file.cpp index b7a6b35199..3da8b7a5b6 100644 --- a/src/load_file.cpp +++ b/src/load_file.cpp @@ -1,17 +1,51 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2021 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 +#include +#include +#include namespace miopen { -std::string LoadFile(const fs::path& p) { return LoadFile(p.string()); } - -std::string LoadFile(const std::string& s) +std::vector LoadFile(const fs::path& path) { - const std::ifstream t(s); - std::stringstream buffer; - buffer << t.rdbuf(); - return buffer.str(); + std::error_code error_code; + const auto size = fs::file_size(path, error_code); + if(error_code) + MIOPEN_THROW(path.string() + ": " + error_code.message()); + std::ifstream in(path, std::ios::binary); + if(!in.is_open()) + MIOPEN_THROW(path.string() + ": file opening error"); + std::vector v(size); + if(in.read(v.data(), v.size()).fail()) + MIOPEN_THROW(path.string() + ": file reading error"); + return v; } } // namespace miopen diff --git a/src/lock_file.cpp b/src/lock_file.cpp index 69fa645136..4b000b8094 100644 --- a/src/lock_file.cpp +++ b/src/lock_file.cpp @@ -31,7 +31,7 @@ namespace miopen { -inline void LogFsError(const fs::filesystem_error& ex, const std::string& from) +inline void LogFsError(const fs::filesystem_error& ex, const std::string_view from) { // clang-format off MIOPEN_LOG_E_FROM(from, "File system operation error in LockFile. " @@ -52,13 +52,13 @@ std::string LockFilePath(const fs::path& filename_) fs::permissions(directory, fs::perms::all); } const auto hash = md5(filename_.parent_path().string()); - const auto file = directory / (hash + "_" + filename_.filename().string() + ".lock"); + const auto file = directory / (hash + "_" + filename_.filename() + ".lock"); return file.string(); } catch(const fs::filesystem_error& ex) { - LogFsError(ex, MIOPEN_GET_FN_NAME()); + LogFsError(ex, MIOPEN_GET_FN_NAME); throw; } } @@ -77,12 +77,12 @@ LockFile::LockFile(const char* path_, PassKey) : path(path_) } catch(const fs::filesystem_error& ex) { - LogFsError(ex, MIOPEN_GET_FN_NAME()); + LogFsError(ex, MIOPEN_GET_FN_NAME); throw; } catch(const boost::interprocess::interprocess_exception& ex) { - LogFlockError(ex, "lock initialization", MIOPEN_GET_FN_NAME()); + LogFlockError(ex, "lock initialization", MIOPEN_GET_FN_NAME); throw; } } diff --git a/src/logger.cpp b/src/logger.cpp index 6c8bc039da..41301920a6 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -191,16 +191,4 @@ std::string LoggingPrefix() return ss.str(); } -/// Expected to be invoked with __func__ and __PRETTY_FUNCTION__. -std::string LoggingParseFunction(const char* func, const char* pretty_func) -{ - std::string fname{func}; - if(fname != "operator()") - return fname; - // lambda - const std::string pf{pretty_func}; - const std::string pf_tail{pf.substr(0, pf.find_first_of('('))}; - return pf_tail.substr(1 + pf_tail.find_last_of(':')); -} - } // namespace miopen diff --git a/src/md5.cpp b/src/md5.cpp index ba6b2c4896..7df276be10 100644 --- a/src/md5.cpp +++ b/src/md5.cpp @@ -42,7 +42,9 @@ #include #include #include +#include #include +#include #define MD5_DIGEST_LENGTH 16 @@ -303,13 +305,13 @@ static void MD5_Final(unsigned char* result, MD5_CTX* ctx) namespace miopen { -std::string md5(std::string s) +std::string md5(const void* data, size_t length) { std::array result{}; MD5_CTX ctx{}; MD5_Init(&ctx); - MD5_Update(&ctx, s.data(), s.length()); + MD5_Update(&ctx, data, length); MD5_Final(result.data(), &ctx); std::ostringstream sout; @@ -319,4 +321,9 @@ std::string md5(std::string s) return sout.str(); } + +std::string md5(const std::string& s) { return md5(s.data(), s.size()); } + +std::string md5(const std::vector& v) { return md5(v.data(), v.size()); } + } // namespace miopen diff --git a/src/mha/mha_descriptor.cpp b/src/mha/mha_descriptor.cpp new file mode 100644 index 0000000000..d0105e60f0 --- /dev/null +++ b/src/mha/mha_descriptor.cpp @@ -0,0 +1,74 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +extern "C" miopenStatus_t miopenCreateMhaDescriptor(miopenMhaDescriptor_t* mhaDesc) +{ + MIOPEN_LOG_FUNCTION(mhaDesc); + return miopen::try_([&] { + auto& desc = miopen::deref(mhaDesc); + desc = new miopen::MhaDescriptor(); + }); +} + +extern "C" miopenStatus_t miopenSetMhaDescriptor(miopenMhaDescriptor_t mhaDesc, float scale) +{ + MIOPEN_LOG_FUNCTION(mhaDesc, scale); + return miopen::try_([&] { miopen::deref(mhaDesc).SetParams(scale); }); +} + +extern "C" miopenStatus_t miopenGetMhaDescriptor(miopenMhaDescriptor_t mhaDesc, float* scale) +{ + MIOPEN_LOG_FUNCTION(mhaDesc); + return miopen::try_([&] { *scale = miopen::deref(mhaDesc).GetScale(); }); +} + +std::ostream& operator<<(std::ostream& stream, const MhaDescriptor& x) +{ + stream << "mha," + << "scale" << x.GetScale() << ","; + + return stream; +} + +void to_json(nlohmann::json& json, const MhaDescriptor& descriptor) +{ + json = nlohmann::json{ + {"scale", descriptor.GetScale()}, + }; +} + +void from_json(const nlohmann::json& json, MhaDescriptor& descriptor) +{ + json.at("scale").get_to(descriptor.scale); +} + +} // namespace miopen diff --git a/src/mha/problem_description.cpp b/src/mha/problem_description.cpp new file mode 100644 index 0000000000..7e88eb41bd --- /dev/null +++ b/src/mha/problem_description.cpp @@ -0,0 +1,93 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +namespace miopen { + +namespace mha { + +NetworkConfig ProblemDescription::MakeNetworkConfig() const +{ + std::ostringstream ss; + + ss << "mha"; + + auto print_strides = [&ss](const TensorDescriptor& desc) { + for(const auto& d : desc.GetStrides()) + { + ss << d << "x"; + } + }; + + if(isForward) + { + ss << "fwd-"; + for(auto s : mhaInputDescsForwardPtr->oDesc.GetLengths()) + { + ss << s << "x"; + } + + print_strides(mhaInputDescsForwardPtr->kDesc); + print_strides(mhaInputDescsForwardPtr->qDesc); + print_strides(mhaInputDescsForwardPtr->vDesc); + print_strides(mhaInputDescsForwardPtr->oDesc); + print_strides(mhaInputDescsForwardPtr->mDesc); + print_strides(mhaInputDescsForwardPtr->zInvDesc); + + ss << mhaInputDescsForwardPtr->oDesc.GetType(); + } + else + { + ss << "bwd-"; + + for(auto s : mhaInputDescsBackwardPtr->oDesc.GetLengths()) + { + ss << s << "x"; + } + print_strides(mhaInputDescsBackwardPtr->kDesc); + print_strides(mhaInputDescsBackwardPtr->qDesc); + print_strides(mhaInputDescsBackwardPtr->vDesc); + print_strides(mhaInputDescsBackwardPtr->oDesc); + print_strides(mhaInputDescsBackwardPtr->doDesc); + print_strides(mhaInputDescsBackwardPtr->mDesc); + print_strides(mhaInputDescsBackwardPtr->zInvDesc); + print_strides(mhaInputDescsBackwardPtr->dqDesc); + print_strides(mhaInputDescsBackwardPtr->dkDesc); + print_strides(mhaInputDescsBackwardPtr->dvDesc); + + ss << mhaInputDescsBackwardPtr->oDesc.GetType(); + } + + return NetworkConfig{ss.str()}; +} + +} // namespace mha + +} // namespace miopen diff --git a/src/mlo_dir_conv.cpp b/src/mlo_dir_conv.cpp index b58888b950..55b450e7fc 100644 --- a/src/mlo_dir_conv.cpp +++ b/src/mlo_dir_conv.cpp @@ -49,18 +49,9 @@ #include #endif -// Only select the first applicable igemm solver due to long compilation time -// (JIRA SWDEV-227826) -/// \todo enable all applicable solvers of igemm after fixing slow compilation -#define WORKAROUND_SWDEV_227826 0 - -#if WORKAROUND_SWDEV_227826 -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS) -#endif - miopen::PerformanceDb miopen::GetDb(const miopen::ExecutionContext& ctx) { - return {ctx.GetPerfDbPath(), ctx.GetUserPerfDbPath()}; + return {DbKinds::PerfDb, ctx.GetPerfDbPath(), ctx.GetUserPerfDbPath()}; } static auto GetGemmSolvers() @@ -260,14 +251,7 @@ std::vector> FindAllImplicitGemmWorkspaceSizes(const miopen::ExecutionContext& ctx, const miopen::conv::ProblemDescription& problem) { -#if WORKAROUND_SWDEV_227826 - if(miopen::IsEnabled(ENV(MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS))) - return GetImplicitGemmSolvers().GetWorkspaceSizes(ctx, problem); - else - return GetImplicitGemmSolvers().GetWorkspaceSizes(ctx, problem, 1); -#else return GetImplicitGemmSolvers().GetWorkspaceSizes(ctx, problem); -#endif } std::vector @@ -275,15 +259,7 @@ FindAllImplicitGemmSolutions(const miopen::ExecutionContext& ctx, const miopen::conv::ProblemDescription& problem, const miopen::AnyInvokeParams& invoke_ctx) { -#if WORKAROUND_SWDEV_227826 - if(miopen::IsEnabled(ENV(MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS))) - return GetImplicitGemmSolvers().SearchForAllSolutions(ctx, problem, GetDb(ctx), invoke_ctx); - else - return GetImplicitGemmSolvers().SearchForAllSolutions( - ctx, problem, GetDb(ctx), invoke_ctx, 1); -#else return GetImplicitGemmSolvers().SearchForAllSolutions(ctx, problem, GetDb(ctx), invoke_ctx); -#endif } std::vector @@ -313,14 +289,7 @@ std::vector> FindImplicitGemmWrWWorkspaceSizes(const miopen::ExecutionContext& ctx, const miopen::conv::ProblemDescription& problem) { -#if WORKAROUND_SWDEV_227826 - if(miopen::IsEnabled(ENV(MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS))) - return GetImplicitGemmWrWSolvers().GetWorkspaceSizes(ctx, problem); - else - return GetImplicitGemmWrWSolvers().GetWorkspaceSizes(ctx, problem, 1); -#else return GetImplicitGemmWrWSolvers().GetWorkspaceSizes(ctx, problem); -#endif } std::vector @@ -328,16 +297,7 @@ FindImplicitGemmWrWAllSolutions(const miopen::ExecutionContext& ctx, const miopen::conv::ProblemDescription& problem, const miopen::AnyInvokeParams& invoke_ctx) { -#if WORKAROUND_SWDEV_227826 - if(miopen::IsEnabled(ENV(MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS))) - return GetImplicitGemmWrWSolvers().SearchForAllSolutions( - ctx, problem, GetDb(ctx), invoke_ctx); - else - return GetImplicitGemmWrWSolvers().SearchForAllSolutions( - ctx, problem, GetDb(ctx), invoke_ctx, 1); -#else return GetImplicitGemmWrWSolvers().SearchForAllSolutions(ctx, problem, GetDb(ctx), invoke_ctx); -#endif } std::vector diff --git a/src/nogpu/handle.cpp b/src/nogpu/handle.cpp index 7172866694..91d16db3bc 100644 --- a/src/nogpu/handle.cpp +++ b/src/nogpu/handle.cpp @@ -187,9 +187,8 @@ Program Handle::LoadProgram(const std::string& program_name, // Save to cache #if MIOPEN_ENABLE_SQLITE_KERN_CACHE - miopen::SaveBinary(p.IsCodeObjectInMemory() - ? p.GetCodeObjectBlob() - : miopen::LoadFile(p.GetCodeObjectPathname().string()), + miopen::SaveBinary(p.IsCodeObjectInMemory() ? p.GetCodeObjectBlob() + : miopen::LoadFile(p.GetCodeObjectPathname()), this->GetTargetProperties(), this->GetMaxComputeUnits(), program_name, diff --git a/src/ocl/batchnormocl.cpp b/src/ocl/batchnormocl.cpp index 6147a827b8..d507bb650b 100644 --- a/src/ocl/batchnormocl.cpp +++ b/src/ocl/batchnormocl.cpp @@ -68,7 +68,8 @@ void BatchNormForwardTraining(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() != yDesc.GetSize() || xDesc.GetSize() != bnScaleBiasMeanVarDesc.GetSize()) + if(xDesc.GetNumDims() != yDesc.GetNumDims() || + xDesc.GetNumDims() != bnScaleBiasMeanVarDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -81,7 +82,7 @@ void BatchNormForwardTraining(Handle& handle, MIOPEN_LOG_E("Only fully packed tensors supported."); MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() < 3) + if(xDesc.GetNumDims() < 3) { MIOPEN_THROW(miopenStatusBadParm); } @@ -185,8 +186,8 @@ void BatchNormForwardInference(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() != yDesc.GetSize() || - xDesc.GetSize() != bnScaleBiasMeanVarDesc.GetSize()) + if(xDesc.GetNumDims() != yDesc.GetNumDims() || + xDesc.GetNumDims() != bnScaleBiasMeanVarDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -194,7 +195,7 @@ void BatchNormForwardInference(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() < 3) + if(xDesc.GetNumDims() < 3) { MIOPEN_THROW(miopenStatusBadParm); } @@ -297,7 +298,8 @@ void BatchNormBackward(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() != dyDesc.GetSize() || xDesc.GetSize() != bnScaleBiasDiffDesc.GetSize()) + if(xDesc.GetNumDims() != dyDesc.GetNumDims() || + xDesc.GetNumDims() != bnScaleBiasDiffDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -305,7 +307,7 @@ void BatchNormBackward(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() < 3) + if(xDesc.GetNumDims() < 3) { MIOPEN_THROW(miopenStatusBadParm); } diff --git a/src/ocl/convolutionocl.cpp b/src/ocl/convolutionocl.cpp index c3a56f52cb..2c54e26c67 100644 --- a/src/ocl/convolutionocl.cpp +++ b/src/ocl/convolutionocl.cpp @@ -204,7 +204,8 @@ static void ShrinkToFind10Results(std::vector& found) static inline std::vector FindConvolution(const ExecutionContext& ctx, const conv::ProblemDescription& problem, - const AnyInvokeParams& invoke_ctx) + const AnyInvokeParams& invoke_ctx, + const int requestAlgoCount) { auto results = std::vector{}; auto sol = boost::optional{}; @@ -214,7 +215,16 @@ static inline std::vector FindConvolution(const ExecutionContext& ctx if(findMode.IsFast(ctx) || findMode.IsHybrid(ctx)) { auto fallback = bool{}; - auto sols = conv.GetSolutions(ctx, problem, 1, &fallback); + auto sols = conv.GetSolutions(ctx, problem, requestAlgoCount, &fallback); + + // Remove solutions for which the given workspace size is insufficient + sols.erase(std::remove_if(sols.begin(), + sols.end(), + [&](const miopenConvSolution_t& entry) { + return invoke_ctx.GetWorkspaceSize() < entry.workspace_size; + }), + sols.end()); + // override the normal find with immed mode with env var if(!sols.empty() && (!(findMode.IsHybrid(ctx) && fallback) || miopen::IsEnabled(ENV(MIOPEN_DEBUG_FORCE_IMMED_MODE_FALLBACK)))) @@ -227,9 +237,12 @@ static inline std::vector FindConvolution(const ExecutionContext& ctx /// It is possible to measure actual execution time and return it to the caller. /// \todo Consider if we need (and want to spend time) for this. const auto id = solver::Id{sol->solution_id}; + const auto& s = id.GetSolver(); CompileSolution(id, ctx, problem); - results.push_back( - {id.GetAlgo(problem.GetDirection()), id.ToString(), sol->time, sol->workspace_size}); + results.push_back({id.GetAlgo(problem.GetDirection()), + id.ToString(), + sol->time, + s.GetWorkspaceSize(ctx, problem)}); } else { @@ -300,7 +313,7 @@ void ConvolutionDescriptor::FindConvFwdAlgorithm(Handle& handle, workSpaceSize, attribute.gfx90aFp16alt.GetFwd()}; - const auto results = FindConvolution(ctx, problem, invoke_ctx); + const auto results = FindConvolution(ctx, problem, invoke_ctx, requestAlgoCount); if(results.empty()) { @@ -361,14 +374,11 @@ void DumpTensorToFileFromDevice(const miopen::Handle& handle, MIOPEN_LOG_E("Directory does not exists : " << path); return; } - std::string file_name_with_path_str = file_name_with_path.string(); - - std::ofstream file_stream; - file_stream.open(file_name_with_path_str); + std::ofstream file_stream{file_name_with_path}; if(!file_stream.is_open()) { - MIOPEN_LOG_E("Cannot write to file : " << file_name_with_path_str); + MIOPEN_LOG_E("Cannot write to file : " << file_name_with_path); return; } @@ -379,10 +389,10 @@ void DumpTensorToFileFromDevice(const miopen::Handle& handle, handle.ReadTo(hdata.data(), dData, num_bytes); MIOPEN_LOG_I2("Done bringing tensor from device to host"); // write tensor data to file - const char* pointer = reinterpret_cast(&hdata[0]); + const char* pointer = hdata.data(); file_stream.write(pointer, num_bytes); file_stream.close(); - MIOPEN_LOG_I("Dumping tensor to file : " << file_name_with_path_str); + MIOPEN_LOG_I("Dumping tensor to file : " << file_name_with_path); } static void ConvForwardCheckNumerics(const Handle& handle, @@ -453,14 +463,14 @@ void ConvolutionDescriptor::ValidateTensors(const ConvTensors& tensors) const } // x_tensor_invalid = - if(tensors.xDesc.GetSize() < 3) + if(tensors.xDesc.GetNumDims() < 3) { MIOPEN_THROW(miopenStatusBadParm, "input tensor's number of dimensions is wrong"); } // tensor_sizes_not_matched = - if(tensors.xDesc.GetSize() != tensors.yDesc.GetSize() || - tensors.xDesc.GetSize() != tensors.wDesc.GetSize()) + if(tensors.xDesc.GetNumDims() != tensors.yDesc.GetNumDims() || + tensors.xDesc.GetNumDims() != tensors.wDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm, "number of dimensions mismatch between input, output and weights tensors"); @@ -891,7 +901,7 @@ void ConvolutionDescriptor::FindConvBwdDataAlgorithm(Handle& handle, workSpaceSize, this->attribute.gfx90aFp16alt.GetBwd()}; - const auto results = FindConvolution(ctx, problem, invoke_ctx); + const auto results = FindConvolution(ctx, problem, invoke_ctx, requestAlgoCount); if(results.empty()) { @@ -1102,7 +1112,7 @@ void ConvolutionDescriptor::FindConvBwdWeightsAlgorithm(Handle& handle, workSpaceSize, attribute.gfx90aFp16alt.GetWrW()}; - const auto results = FindConvolution(ctx, problem, invoke_ctx); + const auto results = FindConvolution(ctx, problem, invoke_ctx, requestAlgoCount); if(results.empty()) { diff --git a/src/ocl/dropoutocl.cpp b/src/ocl/dropoutocl.cpp index f8472fcbb7..fa490cdd0a 100644 --- a/src/ocl/dropoutocl.cpp +++ b/src/ocl/dropoutocl.cpp @@ -181,12 +181,12 @@ void DropoutDescriptor::DropoutForward(const Handle& handle, MIOPEN_THROW(miopenStatusBadParm); } - if(xDesc.GetSize() != yDesc.GetSize()) + if(xDesc.GetNumDims() != yDesc.GetNumDims()) { MIOPEN_THROW("Input/Output dimension does not match"); } - if(xDesc.GetSize() > 5) + if(xDesc.GetNumDims() > 5) { MIOPEN_THROW("Only support 1D to 5D tensors"); } @@ -197,7 +197,7 @@ void DropoutDescriptor::DropoutForward(const Handle& handle, } if(xDesc.GetElementSize() != noise_shape.GetElementSize() || - xDesc.GetSize() != noise_shape.GetSize()) + xDesc.GetNumDims() != noise_shape.GetNumDims()) { MIOPEN_THROW("Only support dropout with regular noise shape currently"); } @@ -277,7 +277,7 @@ void DropoutDescriptor::DropoutForward(const Handle& handle, std::to_string(wk_grp_num) /* + "-noise" + std::to_string(noise_shape.GetLengths()[0])*/; // TODO: Add noise shape - // for(int i = 1; i < noise_shape.GetSize(); i++) + // for(int i = 1; i < noise_shape.GetNumDims(); i++) // network_config += "x" + std::to_string(noise_shape.GetLengths()[i]); auto&& kernels = handle.GetKernels(kernel_name, network_config); @@ -385,12 +385,12 @@ void DropoutDescriptor::DropoutBackward(const Handle& handle, MIOPEN_THROW(miopenStatusBadParm); } - if(dxDesc.GetSize() != dyDesc.GetSize()) + if(dxDesc.GetNumDims() != dyDesc.GetNumDims()) { MIOPEN_THROW("Input/Output dimension does not match"); } - if(dyDesc.GetSize() > 5) + if(dyDesc.GetNumDims() > 5) { MIOPEN_THROW("Only support 1D to 5D tensors"); } @@ -401,7 +401,7 @@ void DropoutDescriptor::DropoutBackward(const Handle& handle, } if(dxDesc.GetElementSize() != noise_shape.GetElementSize() || - dxDesc.GetSize() != noise_shape.GetSize()) + dxDesc.GetNumDims() != noise_shape.GetNumDims()) { MIOPEN_THROW("Only support dropout with regular noise shape currently"); } @@ -482,7 +482,7 @@ void DropoutDescriptor::DropoutBackward(const Handle& handle, std::to_string(wk_grp_num) /* + "-noise" + std::to_string(noise_shape.GetLengths()[0]) */; // TODO: Add noise shape - // for(int i = 1; i < noise_shape.GetSize(); i++) + // for(int i = 1; i < noise_shape.GetNumDims(); i++) // network_config += "x" + std::to_string(noise_shape.GetLengths()[i]); auto&& kernels = handle.GetKernels(kernel_name, network_config); diff --git a/src/ocl/gcn_asm_utils.cpp b/src/ocl/gcn_asm_utils.cpp index 42269fc911..f31321ec7c 100644 --- a/src/ocl/gcn_asm_utils.cpp +++ b/src/ocl/gcn_asm_utils.cpp @@ -182,8 +182,6 @@ std::string AmdgcnAssemble(const std::string& source, std::ostringstream options; options << " -x assembler -target amdgcn--amdhsa"; - if(target.Xnack() && !*target.Xnack()) - options << " -mno-xnack"; /// \todo Hacky way to find out which CO version we need to assemble for. if(params.find("ROCM_METADATA_VERSION=5", 0) == std::string::npos) // Assume that !COv3 == COv2. if(GcnAssemblerSupportsNoCOv3()) // If assembling for COv2, then disable COv3. diff --git a/src/ocl/pooling_ocl.cpp b/src/ocl/pooling_ocl.cpp index 86fca9004b..9881c1596f 100644 --- a/src/ocl/pooling_ocl.cpp +++ b/src/ocl/pooling_ocl.cpp @@ -80,7 +80,7 @@ miopenStatus_t PoolingDescriptor::Forward(Handle& handle, } } - int pool_dim = xDesc.GetSize(); + unsigned pool_dim = xDesc.GetNumDims(); if(pool_dim != 4 && pool_dim != 5) { MIOPEN_THROW("Unsupported pooling dimension"); @@ -171,7 +171,7 @@ miopenStatus_t PoolingDescriptor::Backward(Handle& handle, assert(yDesc.GetElementSize() == dyDesc.GetElementSize() && xDesc.GetElementSize() == dxDesc.GetElementSize()); - int pool_dim = dyDesc.GetSize(); + unsigned pool_dim = dyDesc.GetNumDims(); if(pool_dim != 4 && pool_dim != 5) { MIOPEN_THROW("Unsupported pooling dimension"); diff --git a/src/ocl/rnnocl.cpp b/src/ocl/rnnocl.cpp index b874441b44..7cfedd5d83 100644 --- a/src/ocl/rnnocl.cpp +++ b/src/ocl/rnnocl.cpp @@ -40,21 +40,217 @@ MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_RNNFWD_exp) namespace miopen { -void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, - std::vector& seq_array, - const TensorDescriptor& xDesc, - ConstData_t x, - const TensorDescriptor& hxDesc, - ConstData_t hx, - ConstData_t cx, - const TensorDescriptor& wDesc, - ConstData_t w, - const TensorDescriptor& yDesc, - Data_t y, - Data_t hy, - Data_t cy, - Data_t reserveSpace, - size_t reserveSpaceSize) const +namespace { + +bool RNNForwardMSIsSupported([[maybe_unused]] const RNNDescriptor& desctiptor, + [[maybe_unused]] bool use_dropout) +{ +#if MIOPEN_USE_GEMM && MIOPEN_BACKEND_HIP + if(desctiptor.rnnMode == miopenLSTM && desctiptor.algoMode == miopenRNNdefault && + !use_dropout && desctiptor.nLayers > 1 && desctiptor.dirMode == miopenRNNunidirection && + desctiptor.inputMode != miopenRNNskip && !(miopen::IsDisabled(ENV(MIOPEN_RNNFWD_exp)))) + { + return true; + } +#endif // MIOPEN_USE_GEMM&& MIOPEN_BACKEND_HIP + return false; +} + +void checkGemmStatusAndLog(miopenStatus_t gemm_status) +{ + if(gemm_status != miopenStatusSuccess) + { + if(gemm_status == miopenStatusNotImplemented) + { + MIOPEN_LOG_E("GEMM not implemented"); + } + else + { + MIOPEN_LOG_E("GEMM failed"); + } + } +} + +miopenStatus_t ReducAddBias(miopen::Handle& handle, + Data_t dw, + const Data_t workSpace, + const miopen::TensorDescriptor& dw_desc, + const miopen::TensorDescriptor& ws_desc, + size_t dw_bias_offset, + size_t ws_bias_offset, + Data_t red_workSpace, + size_t red_workSpace_size) +{ + if(ws_desc.GetLengths()[1] != 1) + { + + int algo = getReductionAlgo(); + + switch(algo) + { + case 0: { + float alpha0 = 0; + float alpha1 = 1; + float beta_t = 1; + + OpTensor(handle, + miopenTensorOpAdd, + &alpha0, + dw_desc, + dw, + &alpha1, + ws_desc, + workSpace, + &beta_t, + dw_desc, + dw, + dw_bias_offset, + ws_bias_offset, + dw_bias_offset, + true); + } + break; + case 1: { + float alpha1 = 1; + float beta1 = 1; + + miopen::ReduceTensorDescriptor red_add{ + miopenReduceTensorOp_t::MIOPEN_REDUCE_TENSOR_ADD, + miopenDataType_t::miopenFloat, + miopenNanPropagation_t::MIOPEN_PROPAGATE_NAN, + miopenReduceTensorIndices_t::MIOPEN_REDUCE_TENSOR_NO_INDICES, + miopenIndicesType_t::MIOPEN_32BIT_INDICES}; + + Data_t srcA_with_offset = + static_cast(workSpace) + ws_bias_offset * GetTypeSize(dw_desc.GetType()); + + Data_t dstC_with_offset = + static_cast(dw) + dw_bias_offset * GetTypeSize(dw_desc.GetType()); + + // WA CK bug + Data_t red_workSpace_bugfix = red_workSpace; + if(dw_desc.GetType() == miopenDataType_t::miopenHalf) + { + if(std::align( + 4, red_workSpace_size - 4, red_workSpace_bugfix, red_workSpace_size) == + nullptr) + MIOPEN_THROW(miopenStatusInternalError, "failed alignment."); + } + + red_add.ReduceTensor(handle, + nullptr, + 0, + red_workSpace_bugfix, + red_workSpace_size, + &alpha1, + ws_desc, + srcA_with_offset, + &beta1, + dw_desc, + dstC_with_offset); + } + break; + case 2: + case 3: { + float alpha1 = 1.; + auto red_type = ws_desc.GetType(); + int m = 1, n = ws_desc.GetLengths()[2], k = ws_desc.GetLengths()[1]; + int lda = k, ldb = ws_desc.GetStrides()[1], ldc = n; + + const miopen::TensorDescriptor red_matrix{ + red_type, std::vector{1, 1, k}, std::vector{k, k, 1}}; + + SetTensor(handle, red_matrix, red_workSpace, &alpha1); + + float alpha = 1, beta = 1; + if(algo == 2) + { + miopen::GemmDescriptor gemm_desc = GemmDescriptor{false, + false, + false, + m, + n, + k, + lda, + ldb, + ldc, + 1, // batch count + 0, // Stride A + 0, // Stride B + 0, // Stride C + alpha, // alpha + beta, // beta + red_type, + false}; + + miopenStatus_t gemm_status = CallGemm(handle, + gemm_desc, + red_workSpace, + 0, + workSpace, + ws_bias_offset, + dw, + dw_bias_offset, + GemmBackend_t::rocblas); + checkGemmStatusAndLog(gemm_status); + } + else + { + if(dw_desc.GetType() != miopenDataType_t::miopenFloat) + MIOPEN_THROW(miopenStatusInternalError, "rocblas_sgemv wrong Type"); + + Data_t srcA_with_offset = + static_cast(workSpace) + ws_bias_offset * GetTypeSize(dw_desc.GetType()); + + Data_t dstY_with_offset = + static_cast(dw) + dw_bias_offset * GetTypeSize(dw_desc.GetType()); + + rocblas_sgemv(handle.rhandle().get(), + rocblas_operation::rocblas_operation_none, + n, + k, + &alpha, + static_cast(srcA_with_offset), + ldb, + static_cast(red_workSpace), + 1, + &beta, + static_cast(dstY_with_offset), + 1); + } + } + break; + default: break; + } + } + else + { + // nothing to reduce + // just copy data from workspace to dw + CopyTensor(handle, ws_desc, workSpace, dw_desc, dw, ws_bias_offset, dw_bias_offset); + } + + return miopenStatusSuccess; +} + +} // namespace + +void RNNDescriptor::RNNForwardMS(Handle& handle, + std::vector& seq_array, + const TensorDescriptor& xDesc, + ConstData_t x, + const TensorDescriptor& hxDesc, + ConstData_t hx, + ConstData_t cx, + const TensorDescriptor& wDesc, + ConstData_t w, + const TensorDescriptor& yDesc, + Data_t y, + Data_t hy, + Data_t cy, + Data_t extra_space, + size_t extra_space_size, + miopenRNNFWDMode_t fwd_mode) const { #if MIOPEN_USE_GEMM && MIOPEN_BACKEND_HIP std::vector in_n; @@ -317,7 +513,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, &bacc_per_time, &handle, &xDesc, - reserveSpace, + extra_space, x, w, hidden_size, @@ -352,7 +548,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, const auto x_in_offset = layer > 0 ? RBuff.ht_offset(layer - 1, start_b) : static_cast(start_b * InBuff_strides.batch); - const auto in_ptr = layer > 0 ? reserveSpace : x; + const auto in_ptr = layer > 0 ? extra_space : x; const miopenStatus_t gemm_status = CallGemm(handle, gemm_desc, @@ -360,15 +556,15 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, x_in_offset, w, wx_off, - reserveSpace, + extra_space, out_offset, GemmBackend_t::rocblas); if(gemm_status != miopenStatusSuccess) MIOPEN_THROW("GEMM execution failure"); }; - auto call_bias_add = [&RBuff, &WeiBuf, &handle, &wDesc, reserveSpace, w](int layer, - float beta_t = 0) { + auto call_bias_add = [&RBuff, &WeiBuf, &handle, &wDesc, extra_space, w](int layer, + float beta_t = 0) { float alpha0 = 1; float alpha1 = 1; const auto bias_stride = WeiBuf.bias_stride(); @@ -391,13 +587,13 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, miopenTensorOpAdd, &alpha0, hidden_interim_desc, - reserveSpace, // A + extra_space, // A &alpha1, bias_desc, w, // B &beta_t, hidden_interim_desc, - reserveSpace, // C + extra_space, // C RB_layer_out_off, // A offset w_bias_layer_start_off, // B offset RB_layer_out_off, // C offset @@ -407,13 +603,13 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, miopenTensorOpAdd, &alpha0, hidden_interim_desc, - reserveSpace, + extra_space, &alpha1, bias_desc, w, &beta_t, hidden_interim_desc, - reserveSpace, + extra_space, RB_layer_out_off, w_bias_layer_start_off + bias_stride, RB_layer_out_off, @@ -427,7 +623,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, &in_n, &handle, &xDesc, - reserveSpace, + extra_space, hx, w, hidden_size](int layer, int cur_time) { @@ -467,7 +663,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, const auto RB_layer_save_points_off = RBuff.gemm_write_offset(layer, bacc_per_time[cur_time]); - const auto hx_ptr = cur_time > 0 ? reserveSpace : hx; + const auto hx_ptr = cur_time > 0 ? extra_space : hx; const miopenStatus_t gemm_status = CallGemm(handle, gemm_desc_hx, @@ -475,7 +671,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, hx_ptr_offset, w, WeiBuf.get_matrix_h_off(layer), - reserveSpace, + extra_space, RB_layer_save_points_off, GemmBackend_t::rocblas); @@ -489,7 +685,8 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, &in_n, &handle, &wDesc, - reserveSpace, + fwd_mode, + extra_space, cx, max_batch, hidden_size](int layer_id, int time_id) { @@ -525,7 +722,8 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, LSTMForwardHiddenStateUpdate(handle, wDesc.GetType(), - false, + fwd_mode == miopenRNNFWDMode_t::miopenRNNTraining ? false + : true, is_seq_begin, direction, max_batch, @@ -538,7 +736,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, wei_stride, cx, cx_offset, - reserveSpace, + extra_space, i_offset, f_offset, o_offset, @@ -555,7 +753,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, &in_n, &handle, &wDesc, - reserveSpace, + extra_space, hy, cy, max_batch, @@ -616,7 +814,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, { CopyTensor(handle, src_desc, - reserveSpace, + extra_space, dst_desc, hy, src_batch_offset + RBuff.ht_relative_offset(), @@ -627,7 +825,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, { CopyTensor(handle, src_desc, - reserveSpace, + extra_space, dst_desc, cy, src_batch_offset + RBuff.ct_relative_offset(), @@ -747,10 +945,10 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, hipEventRecord(layer_chunk_end_event[layer_id][chunk_id].get(), stream_pull[stream_id]); }; - { // reserveSpace clean set 0 + { // extra_space clean set 0 const int fill_val = 0; // if(biasMode == 0u) req - hipMemsetAsync(reserveSpace, fill_val, reserveSpaceSize, handle.GetStream()); + hipMemsetAsync(extra_space, fill_val, extra_space_size, handle.GetStream()); } // stage 0 bias and input preload @@ -852,7 +1050,7 @@ void RNNDescriptor::RNNForwardTraining_MS(Handle& handle, auto y_dst_desc = miopen::TensorDescriptor(wDesc.GetType(), y_copy_size, y_dst_stride); CopyTensor( - handle, src_desc, reserveSpace, y_dst_desc, y, RBuff.ht_offset(nLayers - 1, 0), 0); + handle, src_desc, extra_space, y_dst_desc, y, RBuff.ht_offset(nLayers - 1, 0), 0); } sync_root_to_all_stream_pull(); @@ -901,8 +1099,8 @@ void RNNDescriptor::RNNForwardInference(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(hxDesc.GetSize() != cxDesc.GetSize() || hxDesc.GetSize() != hyDesc.GetSize() || - hxDesc.GetSize() != cyDesc.GetSize()) + if(hxDesc.GetNumDims() != cxDesc.GetNumDims() || hxDesc.GetNumDims() != hyDesc.GetNumDims() || + hxDesc.GetNumDims() != cyDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -912,15 +1110,8 @@ void RNNDescriptor::RNNForwardInference(Handle& handle, } #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); + RnnHipAutoProfiler kernel_profiler{handle}; - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } try { #endif @@ -1013,18 +1204,9 @@ void RNNDescriptor::RNNForwardInference(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); + kernel_profiler.abortProfiling(); throw; } - - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } #endif } void RNNDescriptor::RNNForwardInferencePacked(Handle& handle, @@ -1118,6 +1300,26 @@ void RNNDescriptor::RNNForwardInferencePacked(Handle& handle, } // input check end + if(RNNForwardMSIsSupported(*this, false) && xDesc[0].GetType() == miopenFloat && seqLen >= 32) + { + return RNNForwardMS(handle, + in_n, + xDesc[0], + x, + hxDesc, + hx, + cx, + wDesc, + w, + yDesc[0], + y, + hy, + cy, + workSpace, + workSpaceSize, + miopenRNNFWDMode_t::miopenRNNInference); + } + int in_stride = xDesc[0].GetLengths()[1]; int hy_stride = hy_h * bi * static_cast(workspaceScale); int out_stride = out_h; @@ -1425,84 +1627,87 @@ void RNNDescriptor::RNNForwardInferencePacked(Handle& handle, } else { - sp_size[1] = batch_n - in_n.at(0); - sp_size[2] = wei_len; - sp_desc = miopen::TensorDescriptor(wDesc.GetType(), sp_size, sp_stride); - w_size[1] = 1; - w_size[2] = wei_len; - w_desc = miopen::TensorDescriptor(wDesc.GetType(), w_size, w_stride); + if(batch_n - in_n.at(0) > 0) + { + sp_size[1] = batch_n - in_n.at(0); + sp_size[2] = wei_len; + sp_desc = miopen::TensorDescriptor(wDesc.GetType(), sp_size, sp_stride); + w_size[1] = 1; + w_size[2] = wei_len; + w_desc = miopen::TensorDescriptor(wDesc.GetType(), w_size, w_stride); - OpTensor(handle, - miopenTensorOpAdd, - &alpha0, - sp_desc, - workSpace, - &alpha1, - w_desc, - w, - &beta_t, - sp_desc, - workSpace, - hid_shift + in_n.at(0) * hy_stride, - wei_shift_bias_temp, - hid_shift + in_n.at(0) * hy_stride); - // Update time - profileRNNkernels(handle, 1, ctime); + OpTensor(handle, + miopenTensorOpAdd, + &alpha0, + sp_desc, + workSpace, + &alpha1, + w_desc, + w, + &beta_t, + sp_desc, + workSpace, + hid_shift + in_n.at(0) * hy_stride, + wei_shift_bias_temp, + hid_shift + in_n.at(0) * hy_stride); + // Update time + profileRNNkernels(handle, 1, ctime); - if(dirMode != 0u) - { - if(in_n.at(0) == in_n.at(seqLen - 1)) - { - OpTensor(handle, - miopenTensorOpAdd, - &alpha0, - sp_desc, - workSpace, - &alpha1, - w_desc, - w, - &beta_t, - sp_desc, - workSpace, - hid_shift + wei_len, - wei_shift_bias_temp + wei_len, - hid_shift + wei_len, - true); - // Update time - profileRNNkernels(handle, 1, ctime); - } - else + if(dirMode != 0u) { - int cur_batch = 0; - for(int ti = 0; ti < seqLen; ti++) + if(in_n.at(0) == in_n.at(seqLen - 1)) + { + OpTensor(handle, + miopenTensorOpAdd, + &alpha0, + sp_desc, + workSpace, + &alpha1, + w_desc, + w, + &beta_t, + sp_desc, + workSpace, + hid_shift + wei_len, + wei_shift_bias_temp + wei_len, + hid_shift + wei_len, + true); + // Update time + profileRNNkernels(handle, 1, ctime); + } + else { - if(ti != (seqLen - 1)) + int cur_batch = 0; + for(int ti = 0; ti < seqLen; ti++) { - offset = hid_shift + cur_batch * hy_stride; + if(ti != (seqLen - 1)) + { + offset = hid_shift + cur_batch * hy_stride; - sp_size[1] = in_n.at(ti + 1); - sp_size[2] = wei_len; - sp_desc = - miopen::TensorDescriptor(wDesc.GetType(), sp_size, sp_stride); + sp_size[1] = in_n.at(ti + 1); + sp_size[2] = wei_len; + sp_desc = miopen::TensorDescriptor( + wDesc.GetType(), sp_size, sp_stride); - OpTensor(handle, - miopenTensorOpAdd, - &alpha0, - sp_desc, - workSpace, - &alpha1, - w_desc, - w, - &beta_t, - sp_desc, - workSpace, - offset + wei_len, - wei_shift_bias_temp + wei_len, - offset + wei_len); - // Update time - profileRNNkernels(handle, 1, ctime); + OpTensor(handle, + miopenTensorOpAdd, + &alpha0, + sp_desc, + workSpace, + &alpha1, + w_desc, + w, + &beta_t, + sp_desc, + workSpace, + offset + wei_len, + wei_shift_bias_temp + wei_len, + offset + wei_len); + // Update time + profileRNNkernels(handle, 1, ctime); + } + cur_batch += in_n.at(ti); } - cur_batch += in_n.at(ti); } } } @@ -2283,8 +2488,8 @@ void RNNDescriptor::RNNForwardTraining(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(hxDesc.GetSize() != cxDesc.GetSize() || hxDesc.GetSize() != hyDesc.GetSize() || - hxDesc.GetSize() != cyDesc.GetSize()) + if(hxDesc.GetNumDims() != cxDesc.GetNumDims() || hxDesc.GetNumDims() != hyDesc.GetNumDims() || + hxDesc.GetNumDims() != cyDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -2300,15 +2505,7 @@ void RNNDescriptor::RNNForwardTraining(Handle& handle, } #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); - - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } + RnnHipAutoProfiler kernel_profiler{handle}; try { #endif @@ -2398,18 +2595,10 @@ void RNNDescriptor::RNNForwardTraining(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); + kernel_profiler.abortProfiling(); throw; } - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } #endif }; @@ -2437,18 +2626,6 @@ void RNNDescriptor::RNNForwardTrainingPackedTensors( (void)cyDesc; #if MIOPEN_USE_GEMM -#if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); - - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } -#endif - // OCL legacy float ctime = 0.; // reset kernel timer @@ -2516,39 +2693,27 @@ void RNNDescriptor::RNNForwardTrainingPackedTensors( } // input check end bool use_dropout = !float_equal(miopen::deref(dropoutDesc).dropout, 0); -#if MIOPEN_USE_GEMM && MIOPEN_BACKEND_HIP - if(rnnMode == miopenLSTM && algoMode == miopenRNNdefault && !use_dropout && nLayers > 1 && - dirMode == miopenRNNunidirection && inputMode != miopenRNNskip && - !(miopen::IsDisabled(ENV(MIOPEN_RNNFWD_exp))) && xDesc[0].GetType() == miopenFloat && + if(RNNForwardMSIsSupported(*this, use_dropout) && xDesc[0].GetType() == miopenFloat && seqLen >= 32) { - RNNForwardTraining_MS(handle, - in_n, - xDesc[0], - x, - hxDesc, - hx, - cx, - wDesc, - w, - yDesc[0], - y, - hy, - cy, - reserveSpace, - reserveSpaceSize); - - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } - return; + return RNNForwardMS(handle, + in_n, + xDesc[0], + x, + hxDesc, + hx, + cx, + wDesc, + w, + yDesc[0], + y, + hy, + cy, + reserveSpace, + reserveSpaceSize, + miopenRNNFWDMode_t::miopenRNNTraining); } -#endif // MIOPEN_USE_GEMM&& MIOPEN_BACKEND_HIP int in_stride = xDesc[0].GetLengths()[1]; int hy_stride = hy_h * bi * static_cast(workspaceScale); @@ -3751,16 +3916,6 @@ void RNNDescriptor::RNNForwardTrainingPackedTensors( // Update time profileRNNkernels(handle, 2, ctime); -#if MIOPEN_BACKEND_HIP - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } -#endif - #else (void)handle; (void)seqLen; @@ -3818,48 +3973,41 @@ void RNNDescriptor::RNNBackwardData(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(dhyDesc.GetSize() != dcyDesc.GetSize() || dhyDesc.GetSize() != hxDesc.GetSize() || - dhyDesc.GetSize() != cxDesc.GetSize() || dhyDesc.GetSize() != dhxDesc.GetSize() || - dhyDesc.GetSize() != dcxDesc.GetSize()) + if(dhyDesc.GetNumDims() != dcyDesc.GetNumDims() || + dhyDesc.GetNumDims() != hxDesc.GetNumDims() || dhyDesc.GetNumDims() != cxDesc.GetNumDims() || + dhyDesc.GetNumDims() != dhxDesc.GetNumDims() || dhyDesc.GetNumDims() != dcxDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); + RnnHipAutoProfiler kernel_profiler{handle}; - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } try { #endif if(paddingMode == miopenRNNIONotPadded) { - RNNBackwardDataPackedTensors(handle, - seqLen, - dyDesc, - dy, - dhy, - dcy, - w, - hx, - cx, - dxDesc, - dx, - dhxDesc, - dhx, - dcxDesc, - dcx, - workSpace, - workSpaceSize, - reserveSpace, - reserveSpaceSize); + return RNNBackwardDataPackedTensors(handle, + seqLen, + dyDesc, + dy, + dhy, + dcy, + w, + hx, + cx, + dxDesc, + dx, + dhxDesc, + dhx, + dcxDesc, + dcx, + workSpace, + workSpaceSize, + reserveSpace, + reserveSpaceSize); } else { @@ -3925,18 +4073,9 @@ void RNNDescriptor::RNNBackwardData(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); + kernel_profiler.abortProfiling(); throw; } - - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } #endif } @@ -3969,7 +4108,8 @@ void RNNDescriptor::RNNBackwardDataPackedTensors( // if projections supported, dcxDesc.GetLengths()[2] should be used for hidden_size, // dhxDesc.GetLengths()[2] for proj_size. - if(dhxDesc.GetSize() != dcxDesc.GetSize() || dhxDesc.GetLengths()[2] != dcxDesc.GetLengths()[2]) + if(dhxDesc.GetNumDims() != dcxDesc.GetNumDims() || + dhxDesc.GetLengths()[2] != dcxDesc.GetLengths()[2]) { MIOPEN_THROW(miopenStatusBadParm); } @@ -5506,34 +5646,27 @@ void RNNDescriptor::RNNBackwardWeights(Handle& handle, (void)dy; #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); + RnnHipAutoProfiler kernel_profiler{handle}; - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } try { #endif if(paddingMode == miopenRNNIONotPadded) { - RNNBackwardWeightsPackedTensors(handle, - seqLen, - xDesc, - x, - hxDesc, - hx, - dyDesc, - dwDesc, - dw, - workSpace, - workSpaceSize, - reserveSpace, - reserveSpaceSize); + return RNNBackwardWeightsPackedTensors(handle, + seqLen, + xDesc, + x, + hxDesc, + hx, + dyDesc, + dwDesc, + dw, + workSpace, + workSpaceSize, + reserveSpace, + reserveSpaceSize); } else { @@ -5588,18 +5721,9 @@ void RNNDescriptor::RNNBackwardWeights(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); + kernel_profiler.abortProfiling(); throw; } - - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } #endif } @@ -5652,6 +5776,8 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( int hy_h = hxDesc.GetLengths()[2]; int out_h = dyDesc[0].GetLengths()[1]; + miopenDataType_t rnn_data_t = hxDesc.GetType(); + if(in_h <= 0 || hy_h <= 0 || hy_n <= 0 || hy_d <= 0 || out_h <= 0 || seqLen <= 0) { MIOPEN_THROW(miopenStatusBadParm); @@ -5720,11 +5846,10 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( sp_stride[0] = batch_n * hy_stride; sp_stride[1] = hy_stride; - const auto dw_tensor_size = - GetParamsSize(xDesc[0].GetLengths()[1]) / GetTypeSize(dwDesc.GetType()); + const auto dw_tensor_size = GetParamsSize(xDesc[0].GetLengths()[1]) / GetTypeSize(rnn_data_t); w_desc = miopen::TensorDescriptor( - dwDesc.GetType(), {1, 1, dw_tensor_size}, {dw_tensor_size, dw_tensor_size, 1}); + rnn_data_t, {1, 1, dw_tensor_size}, {dw_tensor_size, dw_tensor_size, 1}); SetTensor(handle, w_desc, dw, &beta_t); // Update time @@ -5763,6 +5888,8 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( int hid_shift = li * batch_n * hy_stride; int wei_shift = (in_h + hy_h) * wei_stride + (li - 1) * (bi * hy_h + hy_h) * wei_stride; + size_t dw_bias_offset = wei_shift_bias + static_cast(li) * 2 * wei_stride; + // between layers if(li == 0) { @@ -5783,23 +5910,13 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( 0, // Stride C 1, // alpha 1, // beta - xDesc[0].GetType(), + rnn_data_t, false}; miopenStatus_t gemm_status = CallGemm(handle, gemm_desc, workSpace, 0, x, 0, dw, 0, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); // Update time profileRNNkernels(handle, 1, ctime); } @@ -5829,7 +5946,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( 0, // Stride C 1, // alpha 1, // beta - xDesc[0].GetType(), + rnn_data_t, false}; miopenStatus_t gemm_status = CallGemm(handle, @@ -5842,51 +5959,40 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); // Update time profileRNNkernels(handle, 1, ctime); } if(biasMode != 0u) { - wei_shift = static_cast(wei_shift_bias) + li * 2 * wei_stride; - - sp_size[1] = batch_n; - sp_size[2] = wei_stride; - w_size[1] = 1; - w_size[2] = wei_stride; - w_desc = miopen::TensorDescriptor(dwDesc.GetType(), w_size, w_stride); - sp_desc = miopen::TensorDescriptor(dwDesc.GetType(), sp_size, sp_stride); - - alpha0 = 0; - alpha1 = 1; - beta_t = 1; - - OpTensor(handle, - miopenTensorOpAdd, - &alpha0, - w_desc, - dw, - &alpha1, - sp_desc, - workSpace, - &beta_t, - w_desc, - dw, - wei_shift, - hid_shift, - wei_shift, - true); + const std::vector ws_bias_strides{ + static_cast(batch_n) * hy_stride, static_cast(hy_stride), 1}; + const miopen::TensorDescriptor ws_desc{ + rnn_data_t, + {1, static_cast(batch_n), static_cast(wei_stride)}, + ws_bias_strides}; + + const std::vector dw_bias_strides{ + static_cast(wei_stride), static_cast(wei_stride), 1}; + const miopen::TensorDescriptor dw_desc{ + rnn_data_t, {1, 1, static_cast(wei_stride)}, dw_bias_strides}; + + size_t main_ws_size = + GetMainSolWorkspaceSize(batch_n, miopenRNNTraining, miopenRNNDataSeqMajorNotPadded); + size_t reduction_ws_size = workSpaceSize - main_ws_size; + + Data_t reduction_workSpace = static_cast(workSpace) + main_ws_size; + + ReducAddBias(handle, + dw, + workSpace, + dw_desc, + ws_desc, + dw_bias_offset, + hid_shift, + reduction_workSpace, + reduction_ws_size); // Update time profileRNNkernels(handle, 1, ctime); @@ -5955,7 +6061,18 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( } else { - CopyTensor(handle, w_desc, dw, w_desc, dw, wei_shift - wei_stride, wei_shift); + // second dw bias equal to the first, so just copy reduction result + const std::vector dw_bias_strides{wei_stride, wei_stride, 1}; + const miopen::TensorDescriptor dw_desc{ + rnn_data_t, {1, 1, wei_stride}, dw_bias_strides}; + + CopyTensor(handle, + dw_desc, + dw, + dw_desc, + dw, + dw_bias_offset, + dw_bias_offset + wei_stride); // Update time profileRNNkernels(handle, 1, ctime); } @@ -5966,8 +6083,8 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( sp_size[2] = wei_len; w_size[1] = 1; w_size[2] = wei_len; - w_desc = miopen::TensorDescriptor(dwDesc.GetType(), w_size, w_stride); - sp_desc = miopen::TensorDescriptor(dwDesc.GetType(), sp_size, sp_stride); + w_desc = miopen::TensorDescriptor(rnn_data_t, w_size, w_stride); + sp_desc = miopen::TensorDescriptor(rnn_data_t, sp_size, sp_stride); for(int bs = 0; bs < batch_n; bs++) { @@ -5999,8 +6116,8 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( sp_size[2] = wei_len; w_size[1] = 1; w_size[2] = wei_len; - w_desc = miopen::TensorDescriptor(dwDesc.GetType(), w_size, w_stride); - sp_desc = miopen::TensorDescriptor(dwDesc.GetType(), sp_size, sp_stride); + w_desc = miopen::TensorDescriptor(rnn_data_t, w_size, w_stride); + sp_desc = miopen::TensorDescriptor(rnn_data_t, sp_size, sp_stride); int cur_batch = 0; for(int ti = 0; ti < seqLen - 1; ti++) @@ -6070,7 +6187,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( 0, // Stride C 1, // alpha 1, // beta - xDesc[0].GetType(), + rnn_data_t, false}; miopenStatus_t gemm_status = CallGemm(handle, @@ -6083,17 +6200,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift + ri * wei_len * uni_stride, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); // Update time if(li == nLayers - 1 && ri == bi - 1 && seqLen == 1) @@ -6122,7 +6229,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( 0, // Stride C 1, // alpha 1, // beta - xDesc[0].GetType(), + rnn_data_t, false}; miopenStatus_t gemm_status = @@ -6137,17 +6244,8 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift + ri * wei_len * uni_stride, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); + // Update time profileRNNkernels(handle, 1, ctime); } @@ -6174,7 +6272,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( 0, // Stride C 1, // alpha 1, // beta - xDesc[0].GetType(), + rnn_data_t, false}; miopenStatus_t gemm_status = CallGemm(handle, @@ -6187,17 +6285,8 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift + ri * wei_len * uni_stride, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); + // Update time if(li == nLayers - 1 && ri == bi - 1) profileRNNkernels(handle, 2, ctime); @@ -6235,24 +6324,23 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( { if(hx != nullptr) { - miopen::GemmDescriptor gemm_desc = - GemmDescriptor{false, - true, - false, - wei_len, - hy_h, - in_n.at(cur_time), - hy_stride, - uni_stride, - uni_stride, - 1, // batch count - 0, // Stride A - 0, // Stride B - 0, // Stride C - 1, // alpha - 1, // beta - xDesc[0].GetType(), - false}; + miopen::GemmDescriptor gemm_desc = GemmDescriptor{false, + true, + false, + wei_len, + hy_h, + in_n.at(cur_time), + hy_stride, + uni_stride, + uni_stride, + 1, // batch count + 0, // Stride A + 0, // Stride B + 0, // Stride C + 1, // alpha + 1, // beta + rnn_data_t, + false}; miopenStatus_t gemm_status = CallGemm(handle, @@ -6265,17 +6353,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift + ri * wei_len * uni_stride, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); // Update time if(li == nLayers - 1 && ti == seqLen - 1 && ri == bi - 1) profileRNNkernels(handle, 2, ctime); @@ -6303,7 +6381,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( 0, // Stride C 1, // alpha 1, // beta - xDesc[0].GetType(), + rnn_data_t, false}; miopenStatus_t gemm_status = CallGemm( @@ -6317,17 +6395,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift + ri * wei_len * uni_stride, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); // Update time profileRNNkernels(handle, 1, ctime); } @@ -6337,24 +6405,23 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( if(in_n.at(use_time) > 0) { - miopen::GemmDescriptor gemm_desc = - GemmDescriptor{false, - true, - false, - wei_len, - hy_h, - in_n.at(use_time), - hy_stride, - hy_stride, - uni_stride, - 1, // batch count - 0, // Stride A - 0, // Stride B - 0, // Stride C - 1, // alpha - 1, // beta - xDesc[0].GetType(), - false}; + miopen::GemmDescriptor gemm_desc = GemmDescriptor{false, + true, + false, + wei_len, + hy_h, + in_n.at(use_time), + hy_stride, + hy_stride, + uni_stride, + 1, // batch count + 0, // Stride A + 0, // Stride B + 0, // Stride C + 1, // alpha + 1, // beta + rnn_data_t, + false}; miopenStatus_t gemm_status = CallGemm(handle, @@ -6367,17 +6434,7 @@ void RNNDescriptor::RNNBackwardWeightsPackedTensors( wei_shift + ri * wei_len * uni_stride, GemmBackend_t::rocblas); - if(gemm_status != miopenStatusSuccess) - { - if(gemm_status == miopenStatusNotImplemented) - { - MIOPEN_LOG_E("GEMM not implemented"); - } - else - { - MIOPEN_LOG_E("GEMM failed"); - } - } + checkGemmStatusAndLog(gemm_status); // Update time if(li == nLayers - 1 && ti == seqLen - 1 && ri == bi - 1) profileRNNkernels(handle, 2, ctime); diff --git a/src/ocl/tensorocl.cpp b/src/ocl/tensorocl.cpp index 152da125b0..6fd8a172cf 100644 --- a/src/ocl/tensorocl.cpp +++ b/src/ocl/tensorocl.cpp @@ -1430,15 +1430,14 @@ void SetTensor(const Handle& handle, const TensorDescriptor yDesc_flat = GetFlattenedTensorDescriptor(yDesc); #ifndef NDEBUG - if(yDesc.GetSize() != yDesc_flat.GetSize()) + if(yDesc.GetNumDims() != yDesc_flat.GetNumDims()) { - MIOPEN_LOG_I2(__func__ << std::endl - << "real descriptor: " << yDesc << std::endl - << "flat descriptor: " << yDesc_flat << std::endl); + MIOPEN_LOG_I2("real descriptor: " << yDesc); + MIOPEN_LOG_I2("flat descriptor: " << yDesc_flat); } #endif - const std::size_t yDim_flat = yDesc_flat.GetSize(); + const std::size_t yDim_flat = yDesc_flat.GetNumDims(); assert(yDim_flat > 0 && yDim_flat <= 5); @@ -1584,15 +1583,14 @@ void ScaleTensor(const Handle& handle, const TensorDescriptor yDesc_flat = GetFlattenedTensorDescriptor(yDesc); #ifndef NDEBUG - if(yDesc.GetSize() != yDesc_flat.GetSize()) + if(yDesc.GetNumDims() != yDesc_flat.GetNumDims()) { - MIOPEN_LOG_I2(__func__ << std::endl - << "real descriptor: " << yDesc << std::endl - << "flat descriptor: " << yDesc_flat << std::endl); + MIOPEN_LOG_I2("real descriptor: " << yDesc); + MIOPEN_LOG_I2("flat descriptor: " << yDesc_flat); } #endif - const std::size_t yDim_flat = yDesc_flat.GetSize(); + const std::size_t yDim_flat = yDesc_flat.GetNumDims(); assert(yDim_flat > 0 && yDim_flat <= 5); @@ -1763,17 +1761,16 @@ void CopyTensor(const Handle& handle, const TensorDescriptor& dstDesc_flat = std::get<1>(flat_descriptors); #ifndef NDEBUG - if(srcDesc.GetSize() != srcDesc_flat.GetSize()) + if(srcDesc.GetNumDims() != srcDesc_flat.GetNumDims()) { - MIOPEN_LOG_I2(__func__ << std::endl - << "src real descriptor: " << srcDesc << std::endl - << "src flat descriptor: " << srcDesc_flat << std::endl - << "dst real descriptor: " << dstDesc << std::endl - << "dst flat descriptor: " << dstDesc_flat << std::endl); + MIOPEN_LOG_I2("src real descriptor: " << srcDesc); + MIOPEN_LOG_I2("src flat descriptor: " << srcDesc_flat); + MIOPEN_LOG_I2("dst real descriptor: " << dstDesc); + MIOPEN_LOG_I2("dst flat descriptor: " << dstDesc_flat); } #endif - std::size_t srcDim_flat = srcDesc_flat.GetSize(); + std::size_t srcDim_flat = srcDesc_flat.GetNumDims(); if(srcDim_flat < 1 || srcDim_flat > 5) { @@ -1973,17 +1970,16 @@ void CastTensor(const Handle& handle, const TensorDescriptor& dstDesc_flat = std::get<1>(flat_descriptors); #ifndef NDEBUG - if(srcDesc.GetSize() != srcDesc_flat.GetSize()) + if(srcDesc.GetNumDims() != srcDesc_flat.GetNumDims()) { - MIOPEN_LOG_I2(__func__ << std::endl - << "src real descriptor: " << srcDesc << std::endl - << "src flat descriptor: " << srcDesc_flat << std::endl - << "dst real descriptor: " << dstDesc << std::endl - << "dst flat descriptor: " << dstDesc_flat << std::endl); + MIOPEN_LOG_I2("src real descriptor: " << srcDesc); + MIOPEN_LOG_I2("src flat descriptor: " << srcDesc_flat); + MIOPEN_LOG_I2("dst real descriptor: " << dstDesc); + MIOPEN_LOG_I2("dst flat descriptor: " << dstDesc_flat); } #endif - std::size_t srcDim_flat = srcDesc_flat.GetSize(); + std::size_t srcDim_flat = srcDesc_flat.GetNumDims(); if(srcDim_flat < 1 || srcDim_flat > 5) { @@ -2257,22 +2253,20 @@ void TransformTensor(const Handle& handle, const TensorDescriptor& yDesc_flat = std::get<1>(flat_descriptors); #ifndef NDEBUG - if(xDesc.GetSize() != xDesc_flat.GetSize()) + if(xDesc.GetNumDims() != xDesc_flat.GetNumDims()) { - MIOPEN_LOG_I2(__func__ << std::endl - << "real descriptor: " << xDesc << std::endl - << "flat descriptor: " << xDesc_flat << std::endl); + MIOPEN_LOG_I2("x real descriptor: " << xDesc); + MIOPEN_LOG_I2("x flat descriptor: " << xDesc_flat); } - if(yDesc.GetSize() != yDesc_flat.GetSize()) + if(yDesc.GetNumDims() != yDesc_flat.GetNumDims()) { - MIOPEN_LOG_I2(__func__ << std::endl - << "real descriptor: " << yDesc << std::endl - << "flat descriptor: " << yDesc_flat << std::endl); + MIOPEN_LOG_I2("y real descriptor: " << yDesc); + MIOPEN_LOG_I2("y flat descriptor: " << yDesc_flat); } #endif - const std::size_t yDim_flat = yDesc_flat.GetSize(); + const std::size_t yDim_flat = yDesc_flat.GetNumDims(); assert(yDim_flat > 0 && yDim_flat <= 5); diff --git a/src/pooling.cpp b/src/pooling.cpp index 91a27f324a..a65cb3c0ab 100644 --- a/src/pooling.cpp +++ b/src/pooling.cpp @@ -215,10 +215,10 @@ void PoolingDescriptor::GetForwardOutputDimNd(const TensorDescriptor& xDesc, TensorDescriptor PoolingDescriptor::GetForwardOutputTensor(const TensorDescriptor& xDesc) const { - std::vector out_dim(xDesc.GetSize()); - GetForwardOutputDimNd(xDesc, xDesc.GetSize(), out_dim.data()); + std::vector out_dim(xDesc.GetNumDims()); + GetForwardOutputDimNd(xDesc, xDesc.GetNumDims(), out_dim.data()); - const std::string default_layout = tensor_layout_get_default(xDesc.GetSize()); + const std::string default_layout = tensor_layout_get_default(xDesc.GetNumDims()); const std::string in_layout = xDesc.GetLayout(default_layout); std::vector out_strides; tensor_layout_to_strides(out_dim, default_layout, in_layout, out_strides); @@ -233,7 +233,7 @@ std::size_t PoolingDescriptor::GetWorkSpaceSize(const TensorDescriptor& yDesc) c const auto main_ws = GetMode() == miopenPoolingMax ? y_size * index_e_size : 0; - const auto labels = tensor_layout_get_default(yDesc.GetSize()); + const auto labels = tensor_layout_get_default(yDesc.GetNumDims()); std::size_t transpose_ws = 0; if(yDesc.GetLayout(labels) != labels) diff --git a/src/pooling_api.cpp b/src/pooling_api.cpp index 321194ffe5..fe7c321d0b 100644 --- a/src/pooling_api.cpp +++ b/src/pooling_api.cpp @@ -41,7 +41,7 @@ inline void Pooling_logging_cmd(const miopenPoolingDescriptor_t poolDesc, { if(miopen::IsLoggingCmd()) { - auto tensor_dim = miopen::deref(tensorDesc).GetSize(); + auto tensor_dim = miopen::deref(tensorDesc).GetNumDims(); std::stringstream ss; switch(miopen::deref(tensorDesc).GetType()) diff --git a/src/problem.cpp b/src/problem.cpp index 2ea839391c..bcd14d9589 100644 --- a/src/problem.cpp +++ b/src/problem.cpp @@ -30,7 +30,11 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -116,6 +120,7 @@ void VisitType(int id, Args... args) static Data_t AllocateTensor(Handle& handle, const FindOptions& options, std::vector& owned, + std::vector& owned_scalars, miopenTensorArgumentId_t id, const TensorDescriptor& descriptor) { @@ -124,14 +129,12 @@ static Data_t AllocateTensor(Handle& handle, if(preallocated != options.preallocated_tensors.end()) return preallocated->second; + if((id & miopenTensorArgumentIsScalar) == miopenTensorArgumentIsScalar) + return &owned_scalars.emplace_back(0); + const auto element_size = get_data_size(descriptor.GetType()); auto buffer = handle.Create(descriptor.GetElementSpace() * element_size); - visit_float(descriptor.GetType(), [&](auto as_float) { - const auto zero = as_float(0.f); - SetTensor(handle, descriptor, buffer.get(), &zero); - }); - const auto allocated = buffer.get(); owned.emplace_back(std::move(buffer)); return allocated; @@ -160,9 +163,10 @@ Problem::FindSolutions(Handle& handle, const FindOptions& options, std::size_t m { auto owned_buffers = std::vector{}; auto buffers = std::unordered_map{}; + auto owned_scalars = std::vector{}; const auto allocate = [&](auto id, auto&& descriptor) { - auto buffer = AllocateTensor(handle, options, owned_buffers, id, descriptor); + auto buffer = AllocateTensor(handle, options, owned_buffers, owned_scalars, id, descriptor); buffers.emplace(id, buffer); return buffer; }; @@ -175,9 +179,15 @@ Problem::FindSolutions(Handle& handle, const FindOptions& options, std::size_t m [&](const ConvolutionDescriptor& op_desc) { return FindSolutionsImpl(handle, options, max_solutions, buffers, op_desc); }, + [&](const SoftmaxDescriptor& op_desc) { + return FindSolutionsImpl(handle, options, max_solutions, buffers, op_desc); + }, [&](const ActivationDescriptor& /*op_desc*/) -> std::vector { MIOPEN_THROW(miopenStatusNotImplemented); }, + [&](const MhaDescriptor& op_desc) { + return FindSolutionsImpl(handle, options, max_solutions, buffers, op_desc); + }, [&](const BiasDescriptor& /*op_desc*/) -> std::vector { MIOPEN_THROW(miopenStatusNotImplemented); }), @@ -277,6 +287,128 @@ activ::ProblemDescription Problem::AsActivation() const } } +mha::ProblemDescription Problem::AsMha() const +{ + const auto& mha_desc = boost::get(operator_descriptor); + + float scale = mha_desc.GetScale(); + + const auto& kDesc = GetTensorDescriptorChecked(miopenTensorMhaK, "miopenTensorMhaK"); + const auto& qDesc = GetTensorDescriptorChecked(miopenTensorMhaQ, "miopenTensorMhaQ"); + const auto& vDesc = GetTensorDescriptorChecked(miopenTensorMhaV, "miopenTensorMhaV"); + + const auto& descaleKDesc = + GetTensorDescriptorChecked(miopenTensorMhaDescaleK, "miopenTensorMhaDescaleK"); + const auto& descaleQDesc = + GetTensorDescriptorChecked(miopenTensorMhaDescaleQ, "miopenTensorMhaDescaleQ"); + const auto& descaleVDesc = + GetTensorDescriptorChecked(miopenTensorMhaDescaleV, "miopenTensorMhaDescaleV"); + const auto& descaleSDesc = + GetTensorDescriptorChecked(miopenTensorMhaDescaleS, "miopenTensorMhaDescaleS"); + + const auto& scaleSDesc = + GetTensorDescriptorChecked(miopenTensorMhaScaleS, "miopenTensorMhaScaleS"); + + const auto& dpDesc = GetTensorDescriptorChecked(miopenTensorMhaDropoutProbability, + "miopenTensorMhaDropoutProbability"); + const auto& dsDesc = + GetTensorDescriptorChecked(miopenTensorMhaDropoutSeed, "miopenTensorMhaDropoutSeed"); + const auto& doffDesc = + GetTensorDescriptorChecked(miopenTensorMhaDropoutOffset, "miopenTensorMhaDropoutOffset"); + + const auto& oDesc = GetTensorDescriptorChecked(miopenTensorMhaO, "miopenTensorMhaO"); + const auto& mDesc = GetTensorDescriptorChecked(miopenTensorMhaM, "miopenTensorMhaM"); + const auto& zInvDesc = GetTensorDescriptorChecked(miopenTensorMhaZInv, "miopenTensorMhaZInv"); + + if(GetDirection() == miopenProblemDirectionForward) + { + mha::MhaInputDescsForward mhaInputDescsForward = { + kDesc, + qDesc, + vDesc, + descaleKDesc, + descaleQDesc, + descaleVDesc, + descaleSDesc, + scaleSDesc, + GetTensorDescriptorChecked(miopenTensorMhaScaleO, "miopenTensorMhaScaleO"), + scale, + dpDesc, + dsDesc, + doffDesc, + oDesc, + GetTensorDescriptorChecked(miopenTensorMhaAmaxO, "miopenTensorMhaAmaxO"), + GetTensorDescriptorChecked(miopenTensorMhaAmaxS, "miopenTensorMhaAmaxS"), + mDesc, + zInvDesc}; + + return {mhaInputDescsForward}; + } + else + { + mha::MhaInputDescsBackward mhaInputDescsBackward = { + kDesc, + qDesc, + vDesc, + oDesc, + GetTensorDescriptorChecked(miopenTensorMhaDO, "miopenTensorMhaDO"), + mDesc, + zInvDesc, + descaleKDesc, + descaleQDesc, + descaleVDesc, + descaleSDesc, + GetTensorDescriptorChecked(miopenTensorMhaDescaleO, "miopenTensorMhaDescaleO"), + GetTensorDescriptorChecked(miopenTensorMhaDescaleDO, "miopenTensorMhaDescaleDO"), + GetTensorDescriptorChecked(miopenTensorMhaDescaleDS, "miopenTensorMhaDescaleDS"), + scaleSDesc, + GetTensorDescriptorChecked(miopenTensorMhaScaleDS, "miopenTensorMhaScaleDS"), + GetTensorDescriptorChecked(miopenTensorMhaScaleDQ, "miopenTensorMhaScaleDQ"), + GetTensorDescriptorChecked(miopenTensorMhaScaleDK, "miopenTensorMhaScaleDK"), + GetTensorDescriptorChecked(miopenTensorMhaScaleDV, "miopenTensorMhaScaleDV"), + scale, + dpDesc, + dsDesc, + doffDesc, + GetTensorDescriptorChecked(miopenTensorMhaDQ, "miopenTensorMhaDQ"), + GetTensorDescriptorChecked(miopenTensorMhaDK, "miopenTensorMhaDK"), + GetTensorDescriptorChecked(miopenTensorMhaDV, "miopenTensorMhaDV"), + GetTensorDescriptorChecked(miopenTensorMhaAmaxDQ, "miopenTensorMhaAmaxDQ"), + GetTensorDescriptorChecked(miopenTensorMhaAmaxDK, "miopenTensorMhaAmaxDK"), + GetTensorDescriptorChecked(miopenTensorMhaAmaxDV, "miopenTensorMhaAmaxDV"), + GetTensorDescriptorChecked(miopenTensorMhaAmaxDS, "miopenTensorMhaAmaxDS")}; + + return {mhaInputDescsBackward}; + } +} + +softmax::ProblemDescription Problem::AsSoftmax() const +{ + const auto& softmax_desc = boost::get(operator_descriptor); + + float alpha = softmax_desc.GetAlpha(); + float beta = softmax_desc.GetBeta(); + + softmax::ProblemDescription problem_description = + (GetDirection() == miopenProblemDirectionForward) + ? softmax::ProblemDescription( + &alpha, + &beta, + GetTensorDescriptorChecked(miopenTensorSoftmaxX, "miopenTensorSoftmaxX"), + GetTensorDescriptorChecked(miopenTensorSoftmaxY, "miopenTensorSoftmaxY"), + softmax_desc.GetAlgorithm(), + softmax_desc.GetMode()) + : softmax::ProblemDescription( + &alpha, + &beta, + GetTensorDescriptorChecked(miopenTensorSoftmaxY, "miopenTensorSoftmaxY"), + GetTensorDescriptorChecked(miopenTensorSoftmaxDY, "miopenTensorSoftmaxDY"), + GetTensorDescriptorChecked(miopenTensorSoftmaxDX, "miopenTensorSoftmaxDX"), + softmax_desc.GetAlgorithm(), + softmax_desc.GetMode()); + return problem_description; +} + std::vector Problem::FindSolutionsImpl(Handle& handle, const FindOptions& options, std::size_t max_solutions, @@ -431,6 +563,114 @@ std::vector Problem::FindSolutionsImpl(Handle& handle, return ret; } +std::vector +Problem::FindSolutionsImpl(Handle& handle, + [[maybe_unused]] const FindOptions& options, + std::size_t max_solutions, + [[maybe_unused]] const Buffers& buffers, + [[maybe_unused]] const SoftmaxDescriptor& softmax_desc) const +{ + auto ret = std::vector(); + + auto ctx = ExecutionContext{&handle}; + + const softmax::ProblemDescription problem_description = AsSoftmax(); + + const auto algo = AlgorithmName{"Softmax"}; + + static solver::softmax::AttnSoftmax attnSoftmaxSolver; + static solver::softmax::Softmax regularSoftmaxSolver; + + std::vector solvers; + + solvers.push_back(&attnSoftmaxSolver); + solvers.push_back(®ularSoftmaxSolver); + + for(auto solver : solvers) + { + if(!solver->IsApplicable(ctx, problem_description)) + { + MIOPEN_LOG_I2(solver->SolverDbId() << ": Not applicable"); + continue; + } + + auto solution = Solution(); + + /// \todo time measurement will be done later. For now we set less time for attention + /// softmax and slightly bigger for regular + solution.SetTime(solver == &attnSoftmaxSolver ? 1.0f : 2.0f); + solution.SetWorkspaceSize(solver->GetWorkspaceSize(ctx, problem_description)); + solution.SetSolver(solver->SolverDbId()); + solution.SetProblem({*this}); + + MIOPEN_LOG_I("Found solution: " << solution.GetSolver().ToString() << " , " + << solution.GetWorkspaceSize() << ", " + << solution.GetTime()); + + ret.emplace_back(std::move(solution)); + + if(ret.size() >= max_solutions) + { + break; + } + } + + return ret; +} + +std::vector +Problem::FindSolutionsImpl(Handle& handle, + [[maybe_unused]] const FindOptions& options, + std::size_t max_solutions, + [[maybe_unused]] const Buffers& buffers, + [[maybe_unused]] const MhaDescriptor& mha_desc) const +{ + auto ret = std::vector{}; + + auto ctx = ExecutionContext{&handle}; + + const mha::ProblemDescription problem_description = AsMha(); + + const auto algo = AlgorithmName{"Mha"}; + + static solver::mha::MhaForward mhaForwardSolver; + static solver::mha::MhaBackward mhaBackwardSolver; + + std::vector solvers = {&mhaForwardSolver, &mhaBackwardSolver}; + + for(auto solver : solvers) + { + if(!solver->IsApplicable(ctx, problem_description)) + { + MIOPEN_LOG_I2(solver->SolverDbId() << ": Not applicable"); + continue; + } + + auto solution = Solution(); + + /// \todo time measurement could be done later. For now we set less time for attention + /// softmax and slightly bigger for regular + solution.SetTime(1.0f); + + solution.SetWorkspaceSize(solver->GetWorkspaceSize(ctx, problem_description)); + solution.SetSolver(solver->SolverDbId()); + solution.SetProblem({*this}); + + MIOPEN_LOG_I("Found solution: " << solution.GetSolver().ToString() << " , " + << solution.GetWorkspaceSize() << ", " + << solution.GetTime()); + + ret.emplace_back(std::move(solution)); + + if(ret.size() >= max_solutions) + { + break; + } + } + + return ret; +} + void Problem::ValidateGroupCount(const TensorDescriptor& xDesc, const TensorDescriptor& wDesc, const ConvolutionDescriptor& conv) @@ -456,7 +696,9 @@ void Problem::LogDriverCommand() const const auto log_function = boost::hof::match([&](const ConvolutionDescriptor& op_desc) { LogDriverCommand(op_desc); }, [&](const ActivationDescriptor& op_desc) { LogDriverCommand(op_desc); }, - [&](const BiasDescriptor&) {}); + [&](const BiasDescriptor&) {}, + [&](const MhaDescriptor&) {}, + [&](const SoftmaxDescriptor&) {}); boost::apply_visitor(log_function, operator_descriptor); } @@ -576,6 +818,9 @@ void Problem::CalculateOutput() [&](const ActivationDescriptor&) { RegisterTensorDescriptor(GetOutputId(), GetInput()); }, + + [&](const MhaDescriptor&) { RegisterTensorDescriptor(GetOutputId(), GetInput()); }, + [&](const SoftmaxDescriptor&) { RegisterTensorDescriptor(GetOutputId(), GetInput()); }, [&](const BiasDescriptor&) { RegisterTensorDescriptor(GetOutputId(), GetInput()); }), operator_descriptor); } @@ -585,7 +830,9 @@ miopenTensorArgumentId_t Problem::GetInputId() const return boost::apply_visitor( boost::hof::match([](const ConvolutionDescriptor&) { return miopenTensorConvolutionX; }, [](const ActivationDescriptor&) { return miopenTensorActivationX; }, - [](const BiasDescriptor&) { return miopenTensorBiasX; }), + [](const BiasDescriptor&) { return miopenTensorBiasX; }, + [](const MhaDescriptor&) { return miopenTensorMhaK; }, + [](const SoftmaxDescriptor&) { return miopenTensorSoftmaxX; }), operator_descriptor); } @@ -594,7 +841,9 @@ miopenTensorArgumentId_t Problem::GetOutputId() const return boost::apply_visitor( boost::hof::match([](const ConvolutionDescriptor&) { return miopenTensorConvolutionY; }, [](const ActivationDescriptor&) { return miopenTensorActivationY; }, - [](const BiasDescriptor&) { return miopenTensorBiasY; }), + [](const BiasDescriptor&) { return miopenTensorBiasY; }, + [](const MhaDescriptor&) { return miopenTensorMhaO; }, + [](const SoftmaxDescriptor&) { return miopenTensorSoftmaxY; }), operator_descriptor); } @@ -623,10 +872,11 @@ std::vector FusedProblem::FindSolutions(Handle& handle, const auto find1_solutions = [&]() { OperatorArgs params; auto owned_buffers = std::vector{}; + auto owned_scalars = std::vector{}; const auto make_invoke_params = [&]() { auto buffer_allocator = [&](auto id, auto&& desc) { - return AllocateTensor(handle, options, owned_buffers, id, desc); + return AllocateTensor(handle, options, owned_buffers, owned_scalars, id, desc); }; return MakeInvokeParams(buffer_allocator, params); @@ -679,7 +929,19 @@ void FusedProblem::AddProblemToPlan(FusionPlanDescriptor& plan, const Problem& p [&](const BiasDescriptor&) { plan.AddOp(std::make_shared( problem.GetTensorDescriptorChecked(miopenTensorBias, "miopenTensorBias"))); + }, + [&](const MhaDescriptor&) { + // Not implemented + assert(false); + MIOPEN_THROW(miopenStatusNotImplemented, "Mha is not implemented for FusedProblem"); + }, + [&](const SoftmaxDescriptor&) { + // Not implemented + assert(false); + MIOPEN_THROW(miopenStatusNotImplemented, + "Softmax is not implemented for FusedProblem"); }), + problem.operator_descriptor); } @@ -692,11 +954,23 @@ fusion::FusionInvokeParams FusedProblem::MakeInvokeParams( auto& out_desc = problems.back().GetOutput(); const auto get_buffer = [&](auto id, auto&& descriptor) { + if(const auto found = buffers.find(id); found != buffers.end()) + return found->second; auto buffer = buffer_getter(id, descriptor); buffers.emplace(id, buffer); return buffer; }; + // This is not used right now, but there is a PR using it already and it is an example on how to + // get a scalar. + const auto get_scalar = [&](auto id, auto type_marker) { + // This is hacky because we lack separate way to pass them through API + return *reinterpret_cast*>( + get_buffer(id, TensorDescriptor())); + }; + + std::ignore = get_scalar; + bool gfx90aaltimpl = false; auto in = get_buffer(GetInputId(), in_desc); auto out = get_buffer(GetOutputId(), out_desc); @@ -741,7 +1015,21 @@ fusion::FusionInvokeParams FusedProblem::MakeInvokeParams( const auto bias_ptr = buffers.at(miopenTensorBias); operator_args.params.emplace_back( std::make_unique(bias_ptr)); + }, + + [&](const MhaDescriptor&) { + // Not implemented + assert(false); + MIOPEN_THROW(miopenStatusNotImplemented, + "Mha is not implemented for FusedProblem"); + }, + [&](const SoftmaxDescriptor&) { + // Not implemented + assert(false); + MIOPEN_THROW(miopenStatusNotImplemented, + "Softmax is not implemented for FusedProblem"); }), + problem.operator_descriptor); } diff --git a/src/process.cpp b/src/process.cpp index 81d8b4c096..9235b951a8 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -54,11 +54,11 @@ struct ProcessImpl // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa constexpr std::size_t BUFFER_CAPACITY = 32767; - TCHAR buffer[BUFFER_CAPACITY]; - std::strncpy(buffer, cmd.data(), BUFFER_CAPACITY); + if(cmd.size() < BUFFER_CAPACITY) + cmd.resize(BUFFER_CAPACITY, '\0'); if(CreateProcess(path.string().c_str(), - buffer, + cmd.data(), nullptr, nullptr, FALSE, diff --git a/src/ramdb.cpp b/src/ramdb.cpp index 8a94f2968f..4f844b851a 100644 --- a/src/ramdb.cpp +++ b/src/ramdb.cpp @@ -86,14 +86,18 @@ static std::chrono::seconds GetLockTimeout() { return std::chrono::seconds{60}; using exclusive_lock = std::unique_lock; -RamDb::RamDb(std::string path, bool is_system) : PlainTextDb(path, is_system) {} +RamDb::RamDb(DbKinds db_kind_, std::string path, bool is_system) + : PlainTextDb(db_kind_, path, is_system) +{ +} -RamDb& RamDb::GetCached(const std::string& path, bool is_system) +RamDb& RamDb::GetCached(DbKinds db_kind_, const std::string& path, bool is_system) { // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) static std::mutex mutex; const std::lock_guard lock{mutex}; + // We don't have to store kind to properly index as different dbs would have different paths // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) static auto instances = std::map{}; const auto it = instances.find(path); @@ -108,7 +112,7 @@ RamDb& RamDb::GetCached(const std::string& path, bool is_system) // footprint in heap is very small. That is why we can omit deletion of // these objects thus avoiding bothering with MP/MT syncronization. // These will be destroyed altogether with heap. - auto instance = new RamDb{path, is_system}; + auto instance = new RamDb{db_kind_, path, is_system}; instances.emplace(path, instance); if(!DisableUserDbFileIO) { diff --git a/src/readonlyramdb.cpp b/src/readonlyramdb.cpp index ca47aea3ad..77209654b5 100644 --- a/src/readonlyramdb.cpp +++ b/src/readonlyramdb.cpp @@ -49,12 +49,14 @@ bool& rordb_embed_fs_override() } } // namespace debug -ReadonlyRamDb& ReadonlyRamDb::GetCached(const std::string& path, bool warn_if_unreadable) +ReadonlyRamDb& +ReadonlyRamDb::GetCached(DbKinds db_kind_, const std::string& path, bool warn_if_unreadable) { // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) static std::mutex mutex; const std::lock_guard lock{mutex}; + // We don't have to store kind to properly index as different dbs would have different paths // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) static auto instances = std::map{}; const auto it = instances.find(path); @@ -69,7 +71,8 @@ ReadonlyRamDb& ReadonlyRamDb::GetCached(const std::string& path, bool warn_if_un // footprint in heap is very small. That is why we can omit deletion of // these objects thus avoiding bothering with MP/MT syncronization. // These will be destroyed altogether with heap. - auto instance = new ReadonlyRamDb{path}; + // NOLINTNEXTLINE (cppcoreguidelines-owning-memory) + auto instance = new ReadonlyRamDb{db_kind_, path}; instances.emplace(path, instance); instance->Prefetch(warn_if_unreadable); return *instance; @@ -135,10 +138,11 @@ void ReadonlyRamDb::Prefetch(bool warn_if_unreadable) { #if MIOPEN_EMBED_DB fs::path filepath(db_path); - const auto& it_p = miopen_data().find(filepath.filename().string() + ".o"); + const auto& it_p = + miopen_data().find(make_object_file_name(filepath.filename()).string()); if(it_p == miopen_data().end()) MIOPEN_THROW(miopenStatusInternalError, - "Unknown database: " + filepath.filename().string() + + "Unknown database: " + filepath.filename() + " in internal filesystem"); const auto& p = it_p->second; diff --git a/src/reducetensor.cpp b/src/reducetensor.cpp index eb1ce895b1..64faff383d 100644 --- a/src/reducetensor.cpp +++ b/src/reducetensor.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -520,6 +521,10 @@ ReduceTensorDescriptor::ReduceTensorDescriptor(miopenReduceTensorOp_t reduceTens MIOPEN_THROW("Only int32 type is supported for ReduceTensor indices."); }; +// This is WS requirement of the dynamic reduction. +// We must enforce it especially when reduction is used internally. +constexpr std::size_t workspaceAlignRequirementBytes = 64; + // return the size of the workspace in bytes, so that the workspace buffer can be prepared by the // user std::size_t ReduceTensorDescriptor::GetWorkspaceSize(const Handle& handle, @@ -568,7 +573,7 @@ std::size_t ReduceTensorDescriptor::GetWorkspaceSize(const Handle& handle, std::size_t wsSizeInBytes = !need_indices ? workspace_size * detail::GetDataTypeSize(inDesc.GetType()) : workspace_size * (detail::GetDataTypeSize(inDesc.GetType()) + sizeof(int)) + - 64 + sizeof(int); + 64 + sizeof(int) + workspaceAlignRequirementBytes; // dynamic reduction use one additional page for storing tensor descriptors if(!miopen::IsDisabled(ENV(MIOPEN_DEBUG_DYNAMIC_REDUCTION))) @@ -1031,6 +1036,14 @@ void ReduceTensorDescriptor::ReduceTensor(const Handle& handle, std::to_string(static_cast(use_padding.first)) + std::to_string(static_cast(use_padding.second)); + if(nullptr == std::align(workspaceAlignRequirementBytes, + ws_sizeInBytes - workspaceAlignRequirementBytes, + workspace, + workspaceSizeInBytes)) + { + MIOPEN_THROW(miopenStatusInternalError, "Alignment failed. There is not enough space."); + } + if(!reduceAllDims) { handle.AddKernel( diff --git a/src/rnn.cpp b/src/rnn.cpp index 465a29f040..c23b6a0bc7 100644 --- a/src/rnn.cpp +++ b/src/rnn.cpp @@ -458,7 +458,7 @@ size_t RNNDescriptor::GetMainSolWorkspaceSize(size_t batchLenSum, return (workspaceScale * nLayers * batchLenSum * hsize * typeSize) * (is_bidirect ? 2 : 1); } -size_t RNNDescriptor::GetWorkspaceSize(Handle& /* handle */, +size_t RNNDescriptor::GetWorkspaceSize(Handle& handle, const SeqTensorDescriptor& xDesc, miopenRNNFWDMode_t fwdMode) const { @@ -478,7 +478,15 @@ size_t RNNDescriptor::GetWorkspaceSize(Handle& /* handle */, const std::size_t total_sequence_len = xDesc.GetTotalSequenceLen(); - return transformer_tmp_space + + size_t reduction_ws = ReductionWorkspaceSize(handle, + total_sequence_len, + nHiddenTensorsPerLayer, + workspaceScale, + hsize, + dirMode == miopenRNNbidirection, + dataType); + + return transformer_tmp_space + reduction_ws + GetMainSolWorkspaceSize(total_sequence_len, fwdMode, miopenRNNDataSeqMajorNotPadded); } @@ -499,7 +507,7 @@ size_t RNNDescriptor::GetMaxWorkspaceSize(Handle& handle, } // legacy -size_t RNNDescriptor::GetWorkspaceSize(Handle& /* handle */, +size_t RNNDescriptor::GetWorkspaceSize(Handle& handle, const int seqLength, c_array_view xDesc) const { @@ -519,13 +527,21 @@ size_t RNNDescriptor::GetWorkspaceSize(Handle& /* handle */, std::size_t total_sequence_len = 0; total_sequence_len = std::accumulate( - xDesc.data, xDesc.data + seqLength, 0, [](size_t x, miopenTensorDescriptor_t y) { + xDesc.data, xDesc.data + seqLength, 0ULL, [](size_t x, miopenTensorDescriptor_t y) { return x + deref(y).GetLengths()[0]; }); - return padding_converter_tmp_space + GetMainSolWorkspaceSize(total_sequence_len, - miopenRNNInference, - miopenRNNDataSeqMajorNotPadded); + size_t reduction_ws = ReductionWorkspaceSize(handle, + total_sequence_len, + nHiddenTensorsPerLayer, + workspaceScale, + hsize, + dirMode == miopenRNNbidirection, + dataType); + + return padding_converter_tmp_space + reduction_ws + + GetMainSolWorkspaceSize( + total_sequence_len, miopenRNNInference, miopenRNNDataSeqMajorNotPadded); } ///////////////////////////////// @@ -573,7 +589,7 @@ size_t RNNDescriptor::GetReserveSize(Handle& /* handle */, } std::size_t inputBatchLenSum = 0; inputBatchLenSum = std::accumulate( - xDesc.data, xDesc.data + seqLength, 0, [](size_t x, miopenTensorDescriptor_t y) { + xDesc.data, xDesc.data + seqLength, 0ULL, [](size_t x, miopenTensorDescriptor_t y) { return x + deref(y).GetLengths()[0]; }); return GetReserveSize(inputBatchLenSum); @@ -630,7 +646,7 @@ size_t RNNDescriptor::GetRNNInputSuperTensorSize(Handle& /* handle */, if(paddingMode == miopenRNNIONotPadded) { inputBatchLenSum = std::accumulate( - xDesc.data, xDesc.data + seqLength, 0, [](size_t x, miopenTensorDescriptor_t y) { + xDesc.data, xDesc.data + seqLength, 0ULL, [](size_t x, miopenTensorDescriptor_t y) { return x + deref(y).GetLengths()[0]; }); } @@ -1284,7 +1300,7 @@ void RNNDescriptor::RNNForward(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(hDesc.GetSize() != cDesc.GetSize()) + if(hDesc.GetNumDims() != cDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -1304,15 +1320,8 @@ void RNNDescriptor::RNNForward(Handle& handle, } #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); + RnnHipAutoProfiler kernel_profiler{handle}; - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } try { #endif @@ -1363,18 +1372,10 @@ void RNNDescriptor::RNNForward(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); + kernel_profiler.abortProfiling(); throw; } - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } #endif } @@ -1404,7 +1405,7 @@ void RNNDescriptor::RNNBackwardData(Handle& handle, { MIOPEN_THROW(miopenStatusBadParm); } - if(hDesc.GetSize() != cDesc.GetSize()) + if(hDesc.GetNumDims() != cDesc.GetNumDims()) { MIOPEN_THROW(miopenStatusBadParm); } @@ -1425,15 +1426,8 @@ void RNNDescriptor::RNNBackwardData(Handle& handle, } #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); + RnnHipAutoProfiler kernel_profiler{handle}; - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } try { #endif @@ -1486,17 +1480,9 @@ void RNNDescriptor::RNNBackwardData(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); - throw; - } + kernel_profiler.abortProfiling(); - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); + throw; } #endif } @@ -1537,15 +1523,8 @@ void RNNDescriptor::RNNBackwardWeights(Handle& handle, } #if MIOPEN_BACKEND_HIP - HipEventPtr start = nullptr; - HipEventPtr stop = nullptr; - bool is_profiling = handle.IsProfilingEnabled(); + RnnHipAutoProfiler kernel_profiler{handle}; - if(is_profiling) - { - handle.EnableProfiling(false); - RNNProfilingBegin(handle, start, stop); - } try { #endif @@ -1584,18 +1563,9 @@ void RNNDescriptor::RNNBackwardWeights(Handle& handle, } catch(...) { - if(is_profiling) - handle.EnableProfiling(true); + kernel_profiler.abortProfiling(); throw; } - - if(is_profiling) - { - float eventTime_mS = RNNProfilingEnd(handle, start, stop); - handle.EnableProfiling(true); - handle.ResetKernelTime(); - handle.AccumKernelTime(eventTime_mS); - } #endif } diff --git a/src/rnn/rnn_util.cpp b/src/rnn/rnn_util.cpp index 376c728347..7bc3dff5eb 100644 --- a/src/rnn/rnn_util.cpp +++ b/src/rnn/rnn_util.cpp @@ -27,9 +27,19 @@ #include #include #include +#include + +MIOPEN_DECLARE_ENV_VAR_UINT64(MIOPEN_RNNWRW_REDUCTION) namespace miopen { +int getReductionAlgo() +{ + return miopen::IsUnset(ENV(MIOPEN_RNNWRW_REDUCTION)) + ? 1 + : miopen::Value(ENV(MIOPEN_RNNWRW_REDUCTION)); +} + void RNNTensorPaddingConverter::ConvertTensorData(const Handle& handle, const TensorDescriptor& padded_tensor_desc, std::vector& bsize_per_time, diff --git a/src/softmax.cpp b/src/softmax.cpp index 04e81fc7de..38b233e2e6 100644 --- a/src/softmax.cpp +++ b/src/softmax.cpp @@ -33,8 +33,73 @@ #include #include +#include + namespace miopen { +extern "C" miopenStatus_t miopenCreateSoftmaxDescriptor(miopenSoftmaxDescriptor_t* softmaxDesc) +{ + MIOPEN_LOG_FUNCTION(softmaxDesc); + return miopen::try_([&] { + auto& desc = miopen::deref(softmaxDesc); + desc = new miopen::SoftmaxDescriptor(); + }); +} + +extern "C" miopenStatus_t miopenSetSoftmaxDescriptor(miopenSoftmaxDescriptor_t softmaxDesc, + float alpha, + float beta, + miopenSoftmaxAlgorithm_t algorithm, + miopenSoftmaxMode_t mode) +{ + + MIOPEN_LOG_FUNCTION(softmaxDesc, alpha, beta, algorithm, mode); + return miopen::try_( + [&] { miopen::deref(softmaxDesc).SetParams(alpha, beta, algorithm, mode); }); +} + +extern "C" miopenStatus_t miopenGetSoftmaxDescriptor(const miopenSoftmaxDescriptor_t softmaxDesc, + float* alpha, + float* beta, + miopenSoftmaxAlgorithm_t* algorithm, + miopenSoftmaxMode_t* mode) +{ + MIOPEN_LOG_FUNCTION(softmaxDesc); + return miopen::try_([&] { + *alpha = miopen::deref(softmaxDesc).GetAlpha(); + *beta = miopen::deref(softmaxDesc).GetBeta(); + *algorithm = miopen::deref(softmaxDesc).GetAlgorithm(); + *mode = miopen::deref(softmaxDesc).GetMode(); + }); +} + +std::ostream& operator<<(std::ostream& stream, const SoftmaxDescriptor& x) +{ + stream << "softmax," + << "alpha" << x.GetAlpha() << ",beta" << x.GetBeta() << ",algorithm" << x.GetAlgorithm() + << ",mode" << x.GetMode() << ","; + + return stream; +} + +void to_json(nlohmann::json& json, const SoftmaxDescriptor& descriptor) +{ + json = nlohmann::json{ + {"alpha", descriptor.GetAlpha()}, + {"beta", descriptor.GetBeta()}, + {"algorithm", descriptor.GetAlgorithm()}, + {"mode", descriptor.GetMode()}, + }; +} + +void from_json(const nlohmann::json& json, SoftmaxDescriptor& descriptor) +{ + json.at("alpha").get_to(descriptor.alpha); + json.at("beta").get_to(descriptor.beta); + json.at("algorithm").get_to(descriptor.algorithm); + json.at("mode").get_to(descriptor.mode); +} + miopenStatus_t SoftmaxForward(Handle& handle, const void* alpha, const void* beta, diff --git a/src/softmax/problem_description.cpp b/src/softmax/problem_description.cpp index e40a725617..11b4d6e7b2 100644 --- a/src/softmax/problem_description.cpp +++ b/src/softmax/problem_description.cpp @@ -35,7 +35,9 @@ namespace softmax { NetworkConfig ProblemDescription::MakeNetworkConfig() const { - std::string network_config = "sfmfwd-"; + std::ostringstream ss; + + ss << "sfmfwd-"; if(isForward) { @@ -45,14 +47,11 @@ NetworkConfig ProblemDescription::MakeNetworkConfig() const std::tie(n_x, c_x, h_x, w_x) = tien<4>(xdxDesc.GetLengths()); std::tie(n_y, c_y, h_y, w_y) = tien<4>(yDesc.GetLengths()); - network_config += "n_x" + std::to_string(n_x) + "c_x" + std::to_string(c_x) + "h_x" + - std::to_string(h_x) + "w_x" + std::to_string(w_x) + - - "n_y" + std::to_string(n_y) + "c_y" + std::to_string(c_y) + "h_y" + - std::to_string(h_y) + "w_y" + std::to_string(w_y); + ss << "n_x" << n_x << "c_x" << c_x << "h_x" << h_x << "w_x" << w_x; + ss << "n_y" << n_y << "c_y" << c_y << "h_y" << h_y << "w_y" << w_y; - network_config += "xpk" + std::to_string(static_cast(xdxDesc.IsPacked())) + "ypk" + - std::to_string(static_cast(yDesc.IsPacked())); + ss << "xpk" << static_cast(xdxDesc.IsPacked()); + ss << "ypk" << static_cast(yDesc.IsPacked()); } else { @@ -64,25 +63,21 @@ NetworkConfig ProblemDescription::MakeNetworkConfig() const std::tie(n_dy, c_dy, h_dy, w_dy) = tien<4>(dyDesc.GetLengths()); std::tie(n_dx, c_dx, h_dx, w_dx) = tien<4>(xdxDesc.GetLengths()); - network_config += "n_y" + std::to_string(n_y) + "c_y" + std::to_string(c_y) + "h_y" + - std::to_string(h_y) + "w_y" + std::to_string(w_y) + - - "n_dy" + std::to_string(n_dy) + "c_dy" + std::to_string(c_dy) + "h_dy" + - std::to_string(h_dy) + "w_dy" + std::to_string(w_dy) + - - "n_dx" + std::to_string(n_dx) + "c_dx" + std::to_string(c_dx) + "h_dx" + - std::to_string(h_dx) + "w_dx" + std::to_string(w_dx); + ss << "n_y" << n_y << "c_y" << c_y << "h_y" << h_y << "w_y" << w_y; + ss << "n_dy" << n_dy << "c_dy" << c_dy << "h_dy" << h_dy << "w_dy" << w_dy; + ss << "n_dx" << n_dx << "c_dx" << c_dx << "h_dx" << h_dx << "w_dx" << w_dx; - network_config += "ypk" + std::to_string(static_cast(yDesc.IsPacked())) + "dypk" + - std::to_string(static_cast(dyDesc.IsPacked())) + "dxpk" + - std::to_string(static_cast(xdxDesc.IsPacked())); + ss << "ypk" << static_cast(yDesc.IsPacked()); + ss << "dypk" << static_cast(dyDesc.IsPacked()); + ss << "dxpk" << static_cast(xdxDesc.IsPacked()); } - network_config += "a" + std::to_string(alpha) + "b" + std::to_string(beta) + "algo" + - std::to_string(static_cast(algorithm)) + "mode" + - std::to_string(static_cast(mode)); + ss << "a" << alpha; + ss << "b" << beta; + ss << "algo" << static_cast(algorithm); + ss << "mode" << static_cast(mode); - return NetworkConfig{network_config}; + return NetworkConfig{ss.str()}; } } // namespace softmax diff --git a/src/solution.cpp b/src/solution.cpp index e146191639..9997f8846f 100644 --- a/src/solution.cpp +++ b/src/solution.cpp @@ -31,6 +31,13 @@ #include #include +#include +#include +#include +#include +#include +#include + #include #include @@ -70,11 +77,17 @@ void Solution::Run(Handle& handle, [&](const ConvolutionDescriptor& op_desc) { RunImpl(handle, inputs, workspace, workspace_size, op_desc); }, + [&](const SoftmaxDescriptor& op_desc) { + RunImpl(handle, inputs, workspace, workspace_size, op_desc); + }, [&](const ActivationDescriptor& /*op_desc*/) { MIOPEN_THROW(miopenStatusNotImplemented); }, [&](const BiasDescriptor& /*op_desc*/) { MIOPEN_THROW(miopenStatusNotImplemented); + }, + [&](const MhaDescriptor& op_desc) { + RunImpl(handle, inputs, workspace, workspace_size, op_desc); }), problem_.GetOperatorDescriptor()); }, @@ -113,8 +126,11 @@ void Solution::LogDriverCommand(const ActivationDescriptor& desc) const void Solution::LogDriverCommand(const Problem& problem_) const { boost::apply_visitor( - boost::hof::match([&](const BiasDescriptor&) { /* \todo: think on how to log bias */ }, - [&](const auto& op_desc) { LogDriverCommand(op_desc); }), + boost::hof::match( + [&](const BiasDescriptor&) { /* \todo: think on how to log bias */ }, + [&](const MhaDescriptor&) { /* \todo: think on how to log mha */ }, + [&](const SoftmaxDescriptor&) { /* \todo: think on how to log softmax */ }, + [&](const auto& op_desc) { LogDriverCommand(op_desc); }), problem_.GetOperatorDescriptor()); } @@ -232,6 +248,235 @@ void Solution::RunImpl(Handle& handle, checkNumericsOutput_(); } +void Solution::RunImpl(Handle& handle, + const std::unordered_map& inputs, + Data_t workspace, + std::size_t workspace_size, + [[maybe_unused]] const MhaDescriptor& mha_desc) +{ + const Problem& problem_casted = boost::get(problem.item); + + const auto get_input_checked = [&](auto name, const std::string& name_str) { + const auto& found = inputs.find(name); + if(found == inputs.end()) + { + MIOPEN_THROW(miopenStatusInvalidValue, + "Problem is missing " + name_str + " tensor descriptor."); + } + auto ret = found->second; + if(!ret.descriptor.has_value()) + ret.descriptor = problem_casted.GetTensorDescriptorChecked(name, name_str); + return ret; + }; + + const mha::ProblemDescription problem_description = problem_casted.AsMha(); + + auto k = get_input_checked(miopenTensorMhaK, "miopenTensorMhaK"); + auto q = get_input_checked(miopenTensorMhaQ, "miopenTensorMhaQ"); + auto v = get_input_checked(miopenTensorMhaV, "miopenTensorMhaV"); + auto o = get_input_checked(miopenTensorMhaO, "miopenTensorMhaO"); + + auto descaleK = get_input_checked(miopenTensorMhaDescaleK, "miopenTensorMhaDescaleK"); + auto descaleQ = get_input_checked(miopenTensorMhaDescaleQ, "miopenTensorMhaDescaleQ"); + auto descaleV = get_input_checked(miopenTensorMhaDescaleV, "miopenTensorMhaDescaleV"); + auto descaleS = get_input_checked(miopenTensorMhaDescaleS, "miopenTensorMhaDescaleS"); + auto scaleS = get_input_checked(miopenTensorMhaScaleS, "miopenTensorMhaScaleS"); + + auto m = get_input_checked(miopenTensorMhaM, "miopenTensorMhaM"); + auto zInv = get_input_checked(miopenTensorMhaZInv, "miopenTensorMhaZInv"); + + auto dropoutProbability = + get_input_checked(miopenTensorMhaDropoutProbability, "miopenTensorMhaDropoutProbability"); + auto dropoutSeed = get_input_checked(miopenTensorMhaDropoutSeed, "miopenTensorMhaDropoutSeed"); + auto dropoutOffset = + get_input_checked(miopenTensorMhaDropoutOffset, "miopenTensorMhaDropoutOffset"); + + const auto invoke_ctx = [&]() -> AnyInvokeParams { + switch(problem_casted.GetDirection()) + { + case miopenProblemDirectionForward: { + + auto scaleO = get_input_checked(miopenTensorMhaScaleO, "miopenTensorMhaScaleO"); + + auto amaxO = get_input_checked(miopenTensorMhaAmaxO, "miopenTensorMhaAmaxO"); + auto amaxS = get_input_checked(miopenTensorMhaAmaxS, "miopenTensorMhaAmaxS"); + + mha::MhaDataForward dataForward = {k.buffer, + q.buffer, + v.buffer, + descaleK.buffer, + descaleQ.buffer, + descaleV.buffer, + descaleS.buffer, + scaleS.buffer, + scaleO.buffer, + dropoutProbability.buffer, + dropoutSeed.buffer, + dropoutOffset.buffer, + o.buffer, + amaxO.buffer, + amaxS.buffer, + m.buffer, + zInv.buffer}; + + return mha::InvokeParams(dataForward, workspace, workspace_size); + } + case miopenProblemDirectionBackward: { + + auto doData = get_input_checked(miopenTensorMhaDO, "miopenTensorMhaDO"); + auto descaleO = get_input_checked(miopenTensorMhaDescaleO, "miopenTensorMhaDescaleO"); + auto descaleDO = + get_input_checked(miopenTensorMhaDescaleDO, "miopenTensorMhaDescaleDO"); + auto descaleDS = + get_input_checked(miopenTensorMhaDescaleDS, "miopenTensorMhaDescaleDS"); + + auto scaleDS = get_input_checked(miopenTensorMhaScaleDS, "miopenTensorMhaScaleDS"); + auto scaleDQ = get_input_checked(miopenTensorMhaScaleDQ, "miopenTensorMhaScaleDQ"); + auto scaleDK = get_input_checked(miopenTensorMhaScaleDK, "miopenTensorMhaScaleDK"); + auto scaleDV = get_input_checked(miopenTensorMhaScaleDV, "miopenTensorMhaScaleDV"); + + auto dq = get_input_checked(miopenTensorMhaDQ, "miopenTensorMhaDQ"); + auto dk = get_input_checked(miopenTensorMhaDK, "miopenTensorMhaDK"); + auto dv = get_input_checked(miopenTensorMhaDV, "miopenTensorMhaDV"); + + auto amaxDQ = get_input_checked(miopenTensorMhaAmaxDQ, "miopenTensorMhaAmaxDQ"); + auto amaxDK = get_input_checked(miopenTensorMhaAmaxDK, "miopenTensorMhaAmaxDK"); + auto amaxDV = get_input_checked(miopenTensorMhaAmaxDV, "miopenTensorMhaAmaxDV"); + auto amaxDS = get_input_checked(miopenTensorMhaAmaxDS, "miopenTensorMhaAmaxDS"); + + mha::MhaDataBackward dataBackward = {k.buffer, q.buffer, + v.buffer, o.buffer, + doData.buffer, m.buffer, + zInv.buffer, descaleK.buffer, + descaleQ.buffer, descaleV.buffer, + descaleS.buffer, descaleO.buffer, + descaleDO.buffer, descaleDS.buffer, + scaleS.buffer, scaleDS.buffer, + scaleDQ.buffer, scaleDK.buffer, + scaleDV.buffer, dropoutProbability.buffer, + dropoutSeed.buffer, dropoutOffset.buffer, + dq.buffer, dk.buffer, + dv.buffer, amaxDQ.buffer, + amaxDK.buffer, amaxDV.buffer, + amaxDS.buffer}; + + return mha::InvokeParams(dataBackward, workspace, workspace_size); + } + + default: MIOPEN_THROW(miopenStatusNotImplemented); + } + }(); + + const auto net_cfg = problem_description.MakeNetworkConfig(); + const auto found_invoker = handle.GetInvoker(net_cfg, GetSolver()); + + if(found_invoker) + { + (*found_invoker)(handle, invoke_ctx); + } + else + { + auto ctx = ExecutionContext{&handle}; + + static solver::mha::MhaForward mhaForward; + static solver::mha::MhaBackward mhaBackward; + + const auto mha_solution = GetSolver().ToString() == mhaForward.SolverDbId() + ? mhaForward.GetSolution(ctx, problem_description) + : mhaBackward.GetSolution(ctx, problem_description); + + decltype(auto) invoker = + handle.PrepareInvoker(*mha_solution.invoker_factory, mha_solution.construction_params); + handle.RegisterInvoker(invoker, net_cfg, GetSolver().ToString()); + invoker(handle, invoke_ctx); + } +} + +void Solution::RunImpl(Handle& handle, + const std::unordered_map& inputs, + Data_t /*workspace*/, + std::size_t /*workspace_size*/, + const SoftmaxDescriptor& softmax_desc) +{ + + const auto& problem_casted = boost::get(problem.item); + + const auto get_input_checked = [&](auto name, const std::string& name_str) { + const auto& found = inputs.find(name); + if(found == inputs.end()) + { + MIOPEN_THROW(miopenStatusInvalidValue, + "Problem is missing " + name_str + " tensor descriptor."); + } + auto ret = found->second; + if(!ret.descriptor.has_value()) + ret.descriptor = problem_casted.GetTensorDescriptorChecked(name, name_str); + return ret; + }; + + const softmax::ProblemDescription problem_description = problem_casted.AsSoftmax(); + + float alpha = softmax_desc.GetAlpha(); + float beta = softmax_desc.GetBeta(); + miopenSoftmaxAlgorithm_t algorithm = softmax_desc.GetAlgorithm(); + miopenSoftmaxMode_t mode = softmax_desc.GetMode(); + + const auto invoke_ctx = [&]() -> AnyInvokeParams { + switch(problem_casted.GetDirection()) + { + case miopenProblemDirectionForward: { + auto x = get_input_checked(miopenTensorSoftmaxX, "miopenTensorSoftmaxX"); + auto y = get_input_checked(miopenTensorSoftmaxY, "miopenTensorSoftmaxY"); + + return softmax::InvokeParams( + &alpha, &beta, *x.descriptor, x.buffer, *y.descriptor, y.buffer, algorithm, mode); + } + case miopenProblemDirectionBackward: { + auto y = get_input_checked(miopenTensorSoftmaxY, "miopenTensorSoftmaxY"); + auto dy = get_input_checked(miopenTensorSoftmaxDY, "miopenTensorSoftmaxDY"); + auto dx = get_input_checked(miopenTensorSoftmaxDX, "miopenTensorSoftmaxDX"); + + return softmax::InvokeParams(&alpha, + &beta, + *y.descriptor, + y.buffer, + *dy.descriptor, + dy.buffer, + *dx.descriptor, + dx.buffer, + algorithm, + mode); + } + + default: MIOPEN_THROW(miopenStatusNotImplemented); + } + }(); + + const auto net_cfg = problem_description.MakeNetworkConfig(); + const auto found_invoker = handle.GetInvoker(net_cfg, GetSolver()); + + if(found_invoker) + { + (*found_invoker)(handle, invoke_ctx); + } + else + { + auto ctx = ExecutionContext{&handle}; + + solver::softmax::Softmax regularSoftmax; + solver::softmax::AttnSoftmax attnSoftmax; + + const auto softmax_solution = GetSolver().ToString() == regularSoftmax.SolverDbId() + ? regularSoftmax.GetSolution(ctx, problem_description) + : attnSoftmax.GetSolution(ctx, problem_description); + + decltype(auto) invoker = handle.PrepareInvoker(*softmax_solution.invoker_factory, + softmax_solution.construction_params); + handle.RegisterInvoker(invoker, net_cfg, GetSolver().ToString()); + invoker(handle, invoke_ctx); + } +} + void Solution::RunImpl(Handle& handle, const std::unordered_map& inputs, Data_t /*workspace*/, diff --git a/src/solver.cpp b/src/solver.cpp index e4800fbd2d..f0560adfff 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -642,6 +644,13 @@ inline SolverRegistrar::SolverRegistrar(IdRegistryData& registry) ++id, conv::ConvHipImplicitGemmGroupWrwXdlops{}, miopenConvolutionAlgoImplicitGEMM); + + Register(registry, ++id, Primitive::Softmax, softmax::Softmax{}.SolverDbId()); + Register(registry, ++id, Primitive::Softmax, softmax::AttnSoftmax{}.SolverDbId()); + + Register(registry, ++id, Primitive::Mha, mha::MhaForward{}.SolverDbId()); + Register(registry, ++id, Primitive::Mha, mha::MhaBackward{}.SolverDbId()); + // IMPORTANT: New solvers should be added to the end of the function! } diff --git a/src/solver/activ/bwd_1.cpp b/src/solver/activ/bwd_1.cpp index 190e56fb03..cdfb737427 100644 --- a/src/solver/activ/bwd_1.cpp +++ b/src/solver/activ/bwd_1.cpp @@ -71,14 +71,14 @@ ConvSolution ActivBwdSolver1::GetSolution(const ExecutionContext&, int hdOutStride = 0; int wdOutStride = 0; - if(dyDesc.GetSize() == 4) + if(dyDesc.GetNumDims() == 4) { std::tie(ndOut, cdOut, hdOut, wdOut) = tien<4>(dyDesc.GetLengths()); std::tie(ndOutStride, cdOutStride, hdOutStride, wdOutStride) = tien<4>(dyDesc.GetStrides()); } - else if(dyDesc.GetSize() < 4 && dyDesc.GetSize() > 0) + else if(dyDesc.GetNumDims() < 4 && dyDesc.GetNumDims() > 0) { - auto tensor_size = dyDesc.GetSize(); + auto tensor_size = dyDesc.GetNumDims(); switch(tensor_size) { case 1: @@ -116,14 +116,14 @@ ConvSolution ActivBwdSolver1::GetSolution(const ExecutionContext&, int hOutStride = 0; int wOutStride = 0; - if(yDesc.GetSize() == 4) + if(yDesc.GetNumDims() == 4) { std::tie(nOut, cOut, hOut, wOut) = tien<4>(yDesc.GetLengths()); std::tie(nOutStride, cOutStride, hOutStride, wOutStride) = tien<4>(yDesc.GetStrides()); } - else if(yDesc.GetSize() < 4 && yDesc.GetSize() > 0) + else if(yDesc.GetNumDims() < 4 && yDesc.GetNumDims() > 0) { - auto tensor_size = yDesc.GetSize(); + auto tensor_size = yDesc.GetNumDims(); switch(tensor_size) { case 1: @@ -162,14 +162,14 @@ ConvSolution ActivBwdSolver1::GetSolution(const ExecutionContext&, int hdInStride = 0; int wdInStride = 0; - if(dxDesc.GetSize() == 4) + if(dxDesc.GetNumDims() == 4) { std::tie(ndIn, cdIn, hdIn, wdIn) = tien<4>(dxDesc.GetLengths()); std::tie(ndInStride, cdInStride, hdInStride, wdInStride) = tien<4>(dxDesc.GetStrides()); } - else if(dxDesc.GetSize() < 4 && dxDesc.GetSize() > 0) + else if(dxDesc.GetNumDims() < 4 && dxDesc.GetNumDims() > 0) { - auto tensor_size = dxDesc.GetSize(); + auto tensor_size = dxDesc.GetNumDims(); switch(tensor_size) { case 1: @@ -208,14 +208,14 @@ ConvSolution ActivBwdSolver1::GetSolution(const ExecutionContext&, int hInStride = 0; int wInStride = 0; - if(xDesc.GetSize() == 4) + if(xDesc.GetNumDims() == 4) { std::tie(nIn, cIn, hIn, wIn) = tien<4>(xDesc.GetLengths()); std::tie(nInStride, cInStride, hInStride, wInStride) = tien<4>(xDesc.GetStrides()); } - else if(xDesc.GetSize() < 4 && xDesc.GetSize() > 0) + else if(xDesc.GetNumDims() < 4 && xDesc.GetNumDims() > 0) { - auto tensor_size = xDesc.GetSize(); + auto tensor_size = xDesc.GetNumDims(); switch(tensor_size) { case 1: diff --git a/src/solver/activ/fwd_1.cpp b/src/solver/activ/fwd_1.cpp index 851b0df591..f2cfb1e825 100644 --- a/src/solver/activ/fwd_1.cpp +++ b/src/solver/activ/fwd_1.cpp @@ -67,14 +67,14 @@ ConvSolution ActivFwdSolver1::GetSolution(const ExecutionContext&, int hOutStride = 0; int wOutStride = 0; - if(yDesc.GetSize() == 4) + if(yDesc.GetNumDims() == 4) { std::tie(nOut, cOut, hOut, wOut) = tien<4>(yDesc.GetLengths()); std::tie(nOutStride, cOutStride, hOutStride, wOutStride) = tien<4>(yDesc.GetStrides()); } - else if(yDesc.GetSize() < 4 && yDesc.GetSize() > 0) + else if(yDesc.GetNumDims() < 4 && yDesc.GetNumDims() > 0) { - auto tensor_size = yDesc.GetSize(); + auto tensor_size = yDesc.GetNumDims(); switch(tensor_size) { case 1: @@ -113,14 +113,14 @@ ConvSolution ActivFwdSolver1::GetSolution(const ExecutionContext&, int hInStride = 0; int wInStride = 0; - if(xDesc.GetSize() == 4) + if(xDesc.GetNumDims() == 4) { std::tie(nIn, cIn, hIn, wIn) = tien<4>(xDesc.GetLengths()); std::tie(nInStride, cInStride, hInStride, wInStride) = tien<4>(xDesc.GetStrides()); } - else if(xDesc.GetSize() < 4 && xDesc.GetSize() > 0) + else if(xDesc.GetNumDims() < 4 && xDesc.GetNumDims() > 0) { - auto tensor_size = xDesc.GetSize(); + auto tensor_size = xDesc.GetNumDims(); switch(tensor_size) { case 1: diff --git a/src/solver/conv_asm_1x1u.cpp b/src/solver/conv_asm_1x1u.cpp index 6405e4c4c7..07a0edbc85 100644 --- a/src/solver/conv_asm_1x1u.cpp +++ b/src/solver/conv_asm_1x1u.cpp @@ -415,9 +415,10 @@ bool PerformanceConfigConvAsm1x1U::RunParameterPredictionModel(const ExecutionCo static const std::string& arch = ctx.GetStream().GetDeviceName(); static const std::string solver = "ConvAsm1x1U"; std::vector features = TransformFeatures(problem, n); - if(ai::tuning::ModelSetParams(arch, solver, features, true, [&](int idx, std::string value) { - return this->ModelApplyToken(idx, value, problem); - })) + if(ai::tuning::ModelSetParams( + arch, solver, problem.GetDirection(), features, true, [&](int idx, std::string value) { + return this->ModelApplyToken(idx, value, problem); + })) { MIOPEN_LOG_I("Params set by AI: " << ToString()); return true; diff --git a/src/solver/conv_asm_1x1u_bias_activ_fused.cpp b/src/solver/conv_asm_1x1u_bias_activ_fused.cpp index 252b9f5e3f..9f63cb8775 100644 --- a/src/solver/conv_asm_1x1u_bias_activ_fused.cpp +++ b/src/solver/conv_asm_1x1u_bias_activ_fused.cpp @@ -38,11 +38,7 @@ #include #include -#if !defined(_WIN32) #include -#else -#include -#endif using half_float::half; diff --git a/src/solver/conv_asm_implicit_gemm_gtc_bwd.cpp b/src/solver/conv_asm_implicit_gemm_gtc_bwd.cpp index 5b785e1ec8..f70b0b52ea 100644 --- a/src/solver/conv_asm_implicit_gemm_gtc_bwd.cpp +++ b/src/solver/conv_asm_implicit_gemm_gtc_bwd.cpp @@ -1057,7 +1057,7 @@ ConvAsmImplicitGemmGTCDynamicBwdXdlops::GetSolution(const ExecutionContext& ctx, kernel.comp_options = options.str(); - MIOPEN_LOG_I2(kernel.kernel_file + ":" + kernel.kernel_name); + MIOPEN_LOG_I2(kernel.kernel_file << ":" << kernel.kernel_name); result.invoker_factory = miopen::conv::MakeImplGemmDynamicBackwardDataInvokerFactory(problem, cfg); diff --git a/src/solver/conv_asm_implicit_gemm_gtc_fwd.cpp b/src/solver/conv_asm_implicit_gemm_gtc_fwd.cpp index 8c900308dc..1fffec04f2 100644 --- a/src/solver/conv_asm_implicit_gemm_gtc_fwd.cpp +++ b/src/solver/conv_asm_implicit_gemm_gtc_fwd.cpp @@ -1594,7 +1594,7 @@ ConvAsmImplicitGemmGTCDynamicFwdXdlops::GetSolution(const ExecutionContext& ctx, kernel.comp_options = options.str(); - MIOPEN_LOG_I2(kernel.kernel_file + ":" + kernel.kernel_name); + MIOPEN_LOG_I2(kernel.kernel_file << ":" << kernel.kernel_name); result.invoker_factory = miopen::conv::MakeImplGemmDynamicForwardInvokerFactory( diff --git a/src/solver/conv_asm_implicit_gemm_gtc_wrw_nhwc.cpp b/src/solver/conv_asm_implicit_gemm_gtc_wrw_nhwc.cpp index ca9981e922..6e660383d3 100644 --- a/src/solver/conv_asm_implicit_gemm_gtc_wrw_nhwc.cpp +++ b/src/solver/conv_asm_implicit_gemm_gtc_wrw_nhwc.cpp @@ -864,6 +864,25 @@ bool ConvAsmImplicitGemmGTCDynamicWrwXdlopsNHWC::IsApplicable( return false; #endif +#if WORKAROUND_ISSUE_2867 + { + const int hi = problem.GetOutHeight(); + const int wi = problem.GetOutWidth(); + const int k = problem.GetInChannels(); + const int c = problem.GetOutChannels(); + const int y = problem.GetWeightsHeight(); + const int x = problem.GetWeightsWidth(); + const auto stride_h = problem.GetKernelStrideH(); + const auto stride_w = problem.GetKernelStrideW(); + const auto pad_h = problem.GetPadH(); + const auto pad_w = problem.GetPadW(); + + if(c == 1 && k == 1 && hi == 1 && wi == 1 && y == 3 && x == 3 && pad_h == 2 && pad_w == 2 && + stride_h == 2 && stride_w == 2) + return false; + } +#endif + const auto device_name = ctx.GetStream().GetDeviceName(); if((device_name != "gfx908") && (device_name != "gfx90a") && (!StartsWith(device_name, "gfx94"))) diff --git a/src/solver/conv_asm_implicit_gemm_v4r1_dynamic.cpp b/src/solver/conv_asm_implicit_gemm_v4r1_dynamic.cpp index f9f8e81e1c..9e5c8c5ce4 100644 --- a/src/solver/conv_asm_implicit_gemm_v4r1_dynamic.cpp +++ b/src/solver/conv_asm_implicit_gemm_v4r1_dynamic.cpp @@ -410,7 +410,7 @@ static inline ConvSolution GetSolutionBase(const ExecutionContext& ctx, kernel.comp_options = options.str(); - MIOPEN_LOG_I2(kernel.kernel_file + ":" + kernel.kernel_name); + MIOPEN_LOG_I2(kernel.kernel_file << ":" << kernel.kernel_name); if(kernel_is_1x1) { diff --git a/src/solver/conv_asm_implicit_gemm_wrw_gtc_dynamic_xdlops.cpp b/src/solver/conv_asm_implicit_gemm_wrw_gtc_dynamic_xdlops.cpp index f6ff1d79a7..f3cb3a36be 100644 --- a/src/solver/conv_asm_implicit_gemm_wrw_gtc_dynamic_xdlops.cpp +++ b/src/solver/conv_asm_implicit_gemm_wrw_gtc_dynamic_xdlops.cpp @@ -930,7 +930,7 @@ ConvAsmImplicitGemmGTCDynamicWrwXdlops::GetSolution(const ExecutionContext& ctx, kernel.comp_options = options.str(); - MIOPEN_LOG_I2(kernel.kernel_file + ":" + kernel.kernel_name); + MIOPEN_LOG_I2(kernel.kernel_file << ":" << kernel.kernel_name); result.construction_params.push_back(kernel); diff --git a/src/solver/conv_asm_implicit_gemm_wrw_v4r1_dynamic.cpp b/src/solver/conv_asm_implicit_gemm_wrw_v4r1_dynamic.cpp index 05d412901d..b4d09a7048 100644 --- a/src/solver/conv_asm_implicit_gemm_wrw_v4r1_dynamic.cpp +++ b/src/solver/conv_asm_implicit_gemm_wrw_v4r1_dynamic.cpp @@ -388,7 +388,7 @@ ConvSolution ConvAsmImplicitGemmV4R1DynamicWrw::GetSolution(const ExecutionConte kernel.comp_options = options.str(); - MIOPEN_LOG_I2(kernel.kernel_file + ":" + kernel.kernel_name); + MIOPEN_LOG_I2(kernel.kernel_file << ":" << kernel.kernel_name); result.construction_params.push_back(kernel); diff --git a/src/solver/conv_direct_naive_conv_bwd.cpp b/src/solver/conv_direct_naive_conv_bwd.cpp index abd286509d..5d8ed5d03d 100644 --- a/src/solver/conv_direct_naive_conv_bwd.cpp +++ b/src/solver/conv_direct_naive_conv_bwd.cpp @@ -49,7 +49,7 @@ bool ConvDirectNaiveConvBwd::IsApplicable(const ExecutionContext& ctx, if(!problem.IsDirectionBackwardData()) return false; - if(!problem.AllTensorsDimsFitIntoInt()) + if(!problem.AllTensorsLengthsFitIntoInt()) return false; if(!problem.IsLayoutDefault() && !problem.IsLayoutNHWC()) return false; diff --git a/src/solver/conv_direct_naive_conv_fwd.cpp b/src/solver/conv_direct_naive_conv_fwd.cpp index 8e38537be4..c9ca46ffeb 100644 --- a/src/solver/conv_direct_naive_conv_fwd.cpp +++ b/src/solver/conv_direct_naive_conv_fwd.cpp @@ -56,7 +56,7 @@ bool ConvDirectNaiveConvFwd::IsApplicable(const ExecutionContext& ctx, if(!problem.IsDirectionForward()) return false; - if(!problem.AllTensorsDimsFitIntoInt()) + if(!problem.AllTensorsLengthsFitIntoInt()) return false; if(problem.IsTensorsCasted()) diff --git a/src/solver/conv_direct_naive_conv_wrw.cpp b/src/solver/conv_direct_naive_conv_wrw.cpp index 95e7e75f7b..c937ec63cc 100644 --- a/src/solver/conv_direct_naive_conv_wrw.cpp +++ b/src/solver/conv_direct_naive_conv_wrw.cpp @@ -56,7 +56,7 @@ bool ConvDirectNaiveConvWrw::IsApplicable(const ExecutionContext& ctx, if(!problem.IsDirectionBackwardWrW()) return false; - if(!problem.AllTensorsDimsFitIntoInt()) + if(!problem.AllTensorsLengthsFitIntoInt()) return false; if(problem.IsTensorsCasted()) { diff --git a/src/solver/conv_hip_implicit_gemm_grouped_bwd_xdlops.cpp b/src/solver/conv_hip_implicit_gemm_grouped_bwd_xdlops.cpp index 34e19d75f7..8aa1c9e771 100644 --- a/src/solver/conv_hip_implicit_gemm_grouped_bwd_xdlops.cpp +++ b/src/solver/conv_hip_implicit_gemm_grouped_bwd_xdlops.cpp @@ -34,10 +34,11 @@ #if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL #include #include +#include #endif #include MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_CONV_IMPLICIT_GEMM_HIP_GROUP_BWD_XDLOPS) - +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_BWD_XDLOPS_AI_HEUR) namespace miopen { namespace solver { namespace conv { @@ -216,15 +217,139 @@ bool ConvHipImplicitGemmGroupBwdXdlops::CheckCKApplicability( { return IsCKApplicable, CKArgs>(problem); } -#endif + +#if MIOPEN_ENABLE_AI_KERNEL_TUNING +static std::vector GetKernelAsTokens(const std::string& kernel) +{ + std::vector tokens; + std::string token; + std::istringstream tokenStream( + kernel.substr(kernel.find('<') + 1, kernel.find('>') - kernel.find('<') - 1)); + while(std::getline(tokenStream, token, ',')) + { + token.erase(remove_if(token.begin(), token.end(), isspace), + token.end()); // strip whitespace + tokens.push_back(token); + } + return tokens; +} + +void PerformanceConfigHipImplicitGemmGroupBwdXdlops::InitHeuristicKernelIDs() +{ + for(int i = 0; i < valid_kernels.size(); i++) + { + if(valid_kernels[i].find("DeviceGroupedConvBwdDataMultipleD_Xdl_CShuffle_v1") != + std::string::npos) + { + heuristic_indexes.push_back(i); + heuristic_kernels.push_back(GetKernelAsTokens(valid_kernels[i])); + } + } +} + +bool PerformanceConfigHipImplicitGemmGroupBwdXdlops::ModelApplyToken(int idx, std::string value) +{ + if(idx == 13) + idx += 1; // skip + + auto eraseBegin = std::remove_if( + heuristic_indexes.begin(), heuristic_indexes.end(), [&](int heuristic_index) { + return heuristic_kernels[heuristic_index][idx] != value; + }); + + if(eraseBegin != heuristic_indexes.begin()) + { + heuristic_indexes.erase(eraseBegin, heuristic_indexes.end()); + return true; + } + return false; +} + +static std::vector GetFeatures(const ProblemDescription& problem) +{ + std::size_t n = 18; + std::vector features(n * n, 0.0f); + features[0] = 0.0; + features[n + 1] = problem.GetOutChannels(); + features[2 * n + 2] = problem.GetOutHeight(); + features[3 * n + 3] = problem.GetOutWidth(); + features[4 * n + 4] = problem.GetInChannels(); + features[5 * n + 5] = problem.GetInHeight(); + features[6 * n + 6] = problem.GetInWidth(); + features[7 * n + 7] = problem.GetWeightsHeight(); + features[8 * n + 8] = problem.GetWeightsWidth(); + features[9 * n + 9] = problem.GetPadH(); + features[10 * n + 10] = problem.GetPadW(); + features[11 * n + 11] = problem.GetKernelStrideH(); + features[12 * n + 12] = problem.GetKernelStrideW(); + features[13 * n + 13] = problem.GetDilationH(); + features[14 * n + 14] = problem.GetDilationW(); + features[15 * n + 15] = problem.GetBatchSize(); + features[16 * n + 16] = problem.GetInDataType() == miopenFloat ? 2.0 : 1.0; + features[17 * n + 17] = problem.GetGroupCount(); + return features; +} + +template +bool PerformanceConfigHipImplicitGemmGroupBwdXdlops::RunParameterPredictionModel( + const ExecutionContext& ctx, const ProblemDescription& problem) +{ + valid_kernels = FillValidKernelsIDs, CKArgs>( + problem); // filter valid_kernel ID's + InitHeuristicKernelIDs(); + static const std::string& arch = ctx.GetStream().GetDeviceName(); + static const std::string solver = "ConvHipIgemmGroupXdlops"; + std::vector features = GetFeatures(problem); + if(ai::tuning::ModelSetParams( + arch, solver, problem.GetDirection(), features, true, [&](int idx, std::string value) { + return this->ModelApplyToken(idx, value); + })) + { + index = heuristic_indexes[0]; + kernel_id = valid_kernels[index]; + MIOPEN_LOG_I("Params set by AI: " << ToString()); + return true; + } + return false; +} +#endif // MIOPEN_ENABLE_AI_KERNEL_TUNING +#endif // MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL + +bool PerformanceConfigHipImplicitGemmGroupBwdXdlops::IsModelApplicable( + const ExecutionContext& ctx, const ProblemDescription& problem) const +{ + if(ctx.GetStream().GetDeviceName() != "gfx90a" && ctx.GetStream().GetDeviceName() != "gfx942") + return false; + if(problem.GetInDataType() != miopenFloat && problem.GetInDataType() != miopenHalf) + return false; + if(miopen::IsDisabled(ENV(MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_BWD_XDLOPS_AI_HEUR))) + return false; + return true; +} void PerformanceConfigHipImplicitGemmGroupBwdXdlops::HeuristicInit( + [[maybe_unused]] const ExecutionContext& ctx, [[maybe_unused]] const ProblemDescription& problem) { index = 0; kernel_id = ""; #if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL +#if MIOPEN_ENABLE_AI_KERNEL_TUNING + if(IsModelApplicable(ctx, problem)) + { + if(problem.GetInDataType() == miopenFloat) + { + if(RunParameterPredictionModel(ctx, problem)) + return; + } + else + { + if(RunParameterPredictionModel(ctx, problem)) + return; + } + } +#endif switch(problem.GetInDataType()) { case miopenHalf: Init(problem); break; @@ -241,9 +366,20 @@ void PerformanceConfigHipImplicitGemmGroupBwdXdlops::HeuristicInit( bool PerformanceConfigHipImplicitGemmGroupBwdXdlops::SetNextValue(const ProblemDescription& problem) { +#if MIOPEN_USE_COMPOSABLEKERNEL if(valid_kernels.empty()) { - HeuristicInit(problem); + switch(problem.GetInDataType()) + { + case miopenHalf: Init(problem); break; + case miopenFloat: Init(problem); break; + case miopenInt8: Init(problem); break; + case miopenInt32: + case miopenBFloat16: + case miopenFloat8: + case miopenBFloat8: + case miopenDouble: break; + } assert(!valid_kernels.empty()); return true; } @@ -254,6 +390,7 @@ bool PerformanceConfigHipImplicitGemmGroupBwdXdlops::SetNextValue(const ProblemD return true; } else +#endif return false; } @@ -289,10 +426,10 @@ bool PerformanceConfigHipImplicitGemmGroupBwdXdlops::operator==( PerformanceConfigHipImplicitGemmGroupBwdXdlops ConvHipImplicitGemmGroupBwdXdlops::GetDefaultPerformanceConfig( - const ExecutionContext&, const ProblemDescription& problem) const + const ExecutionContext& ctx, const ProblemDescription& problem) const { PerformanceConfigHipImplicitGemmGroupBwdXdlops pp; - pp.HeuristicInit(problem); + pp.HeuristicInit(ctx, problem); return pp; } diff --git a/src/solver/conv_hip_implicit_gemm_grouped_fwd_xdlops.cpp b/src/solver/conv_hip_implicit_gemm_grouped_fwd_xdlops.cpp index 3c1ea8fbeb..07cf1826f7 100644 --- a/src/solver/conv_hip_implicit_gemm_grouped_fwd_xdlops.cpp +++ b/src/solver/conv_hip_implicit_gemm_grouped_fwd_xdlops.cpp @@ -32,6 +32,7 @@ #include #include #if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL +#include #include #include #endif @@ -221,45 +222,77 @@ void PerformanceConfigHipImplicitGemmGroupFwdXdlops::InitHeuristicKernelIDs() } } -bool PerformanceConfigHipImplicitGemmGroupFwdXdlops::ModelApplyToken(int idx, std::string value) +bool PerformanceConfigHipImplicitGemmGroupFwdXdlops::ModelApplyToken(int idx, + std::string value, + const std::string& arch) { - - std::vector new_heuristic_indexes; - new_heuristic_indexes.reserve(heuristic_indexes.size()); - if(idx >= 5) - idx += 2; // skip MPerXDL and NPerXDL as they are constant + if(arch == "gfx90a") + { + if(idx >= 5) + { + idx += 2; // skip MPerXDL and NPerXDL as they are constant + } + } auto eraseBegin = std::remove_if( heuristic_indexes.begin(), heuristic_indexes.end(), [&](int heuristic_index) { return heuristic_kernels[heuristic_index][idx] != value; }); - heuristic_indexes.erase(eraseBegin, heuristic_indexes.end()); - - return !heuristic_indexes.empty(); + if(eraseBegin != heuristic_indexes.begin()) + { + heuristic_indexes.erase(eraseBegin, heuristic_indexes.end()); + return true; + } + return false; } -static std::vector GetFeatures(const ProblemDescription& problem, std::size_t num_cu) +static std::vector +GetFeatures(const ProblemDescription& problem, std::size_t num_cu, const std::string& arch) { + if(arch == "gfx90a") + { + std::size_t n = 18; + std::vector features(n, 0.0f); + features[0] = problem.GetInDataType() == miopenFloat ? 2 : 1; + features[1] = problem.GetInChannels(); + features[2] = problem.GetInHeight(); + features[3] = problem.GetInWidth(); + features[4] = problem.GetOutChannels(); + features[5] = problem.GetOutHeight(); + features[6] = problem.GetOutWidth(); + features[7] = problem.GetWeightsHeight(); + features[8] = problem.GetWeightsWidth(); + features[9] = problem.GetPadH(); + features[10] = problem.GetPadW(); + features[11] = problem.GetKernelStrideH(); + features[12] = problem.GetKernelStrideW(); + features[13] = problem.GetDilationH(); + features[14] = problem.GetDilationW(); + features[15] = problem.GetBatchSize(); + features[16] = problem.GetGroupCount(); + features[17] = num_cu; + return features; + } std::size_t n = 18; - std::vector features(n, 0.0f); - features[0] = problem.GetInDataType() == miopenFloat ? 2 : 1; - features[1] = problem.GetInChannels(); - features[2] = problem.GetInHeight(); - features[3] = problem.GetInWidth(); - features[4] = problem.GetOutChannels(); - features[5] = problem.GetOutHeight(); - features[6] = problem.GetOutWidth(); - features[7] = problem.GetWeightsHeight(); - features[8] = problem.GetWeightsWidth(); - features[9] = problem.GetPadH(); - features[10] = problem.GetPadW(); - features[11] = problem.GetKernelStrideH(); - features[12] = problem.GetKernelStrideW(); - features[13] = problem.GetDilationH(); - features[14] = problem.GetDilationW(); - features[15] = problem.GetBatchSize(); - features[16] = problem.GetGroupCount(); - features[17] = num_cu; + std::vector features(n * n, 0.0f); + features[0] = 2.0; + features[n + 1] = problem.GetInChannels(); + features[2 * n + 2] = problem.GetInHeight(); + features[3 * n + 3] = problem.GetInWidth(); + features[4 * n + 4] = problem.GetOutChannels(); + features[5 * n + 5] = problem.GetOutHeight(); + features[6 * n + 6] = problem.GetOutWidth(); + features[7 * n + 7] = problem.GetWeightsHeight(); + features[8 * n + 8] = problem.GetWeightsWidth(); + features[9 * n + 9] = problem.GetPadH(); + features[10 * n + 10] = problem.GetPadW(); + features[11 * n + 11] = problem.GetKernelStrideH(); + features[12 * n + 12] = problem.GetKernelStrideW(); + features[13 * n + 13] = problem.GetDilationH(); + features[14 * n + 14] = problem.GetDilationW(); + features[15 * n + 15] = problem.GetBatchSize(); + features[16 * n + 16] = problem.GetInDataType() == miopenFloat ? 2.0 : 1.0; + features[17 * n + 17] = problem.GetGroupCount(); return features; } @@ -270,12 +303,19 @@ bool PerformanceConfigHipImplicitGemmGroupFwdXdlops::RunParameterPredictionModel valid_kernels = FillValidKernelsIDs, CKArgs>( problem); // filter valid_kernel ID's InitHeuristicKernelIDs(); - static const std::string& arch = ctx.GetStream().GetDeviceName(); - static const std::string solver = "ConvHipIgemmGroupFwdXdlops"; - std::vector features = GetFeatures(problem, ctx.GetStream().GetMaxComputeUnits()); - if(ai::tuning::ModelSetParams(arch, solver, features, false, [&](int idx, std::string value) { - return this->ModelApplyToken(idx, value); - })) + static const std::string& arch = ctx.GetStream().GetDeviceName(); + static const std::string solver = + (arch == "gfx90a") ? "ConvHipIgemmGroupFwdXdlops" : "ConvHipIgemmGroupXdlops"; + std::vector features = GetFeatures(problem, ctx.GetStream().GetMaxComputeUnits(), arch); + bool transform = (arch == "gfx90a") ? false : true; + if(ai::tuning::ModelSetParams(arch, + solver, + problem.GetDirection(), + features, + transform, + [&](int idx, const std::string& value) { + return this->ModelApplyToken(idx, value, arch); + })) { index = heuristic_indexes[0]; kernel_id = valid_kernels[index]; @@ -290,7 +330,7 @@ bool PerformanceConfigHipImplicitGemmGroupFwdXdlops::RunParameterPredictionModel bool PerformanceConfigHipImplicitGemmGroupFwdXdlops::IsModelApplicable( const ExecutionContext& ctx, const ProblemDescription& problem) const { - if(ctx.GetStream().GetDeviceName() != "gfx90a") + if(ctx.GetStream().GetDeviceName() != "gfx90a" && ctx.GetStream().GetDeviceName() != "gfx942") return false; if(problem.GetInDataType() != miopenFloat && problem.GetInDataType() != miopenHalf) return false; @@ -303,7 +343,6 @@ void PerformanceConfigHipImplicitGemmGroupFwdXdlops::HeuristicInit( [[maybe_unused]] const ExecutionContext& ctx, [[maybe_unused]] const ProblemDescription& problem) { - // these seem redundant index = 0; kernel_id = ""; @@ -454,8 +493,7 @@ bool ConvHipImplicitGemmGroupFwdXdlops::IsApplicable( // needed because layout transpose kernel does not support non-packed tensors if(problem.IsLayoutDefault() && problem.HasNonPackedTensors()) return false; - const std::string& arch = ctx.GetStream().GetDeviceName(); - if(!(arch == "gfx908" || arch == "gfx90a")) + if(!ck_utility::is_ck_whitelist(ctx.GetStream().GetDeviceName())) return false; switch(problem.GetInDataType()) { diff --git a/src/solver/conv_hip_implicit_gemm_grouped_wrw_xdlops.cpp b/src/solver/conv_hip_implicit_gemm_grouped_wrw_xdlops.cpp index 5f8a805027..2efbc77ded 100644 --- a/src/solver/conv_hip_implicit_gemm_grouped_wrw_xdlops.cpp +++ b/src/solver/conv_hip_implicit_gemm_grouped_wrw_xdlops.cpp @@ -34,9 +34,11 @@ #if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL #include #include +#include #endif #include MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_WRW_XDLOPS) +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_WRW_XDLOPS_AI_HEUR) namespace miopen { namespace solver { @@ -212,15 +214,138 @@ bool ConvHipImplicitGemmGroupWrwXdlops::CheckCKApplicability( { return IsCKApplicable, CKArgs>(problem); } -#endif + +#if MIOPEN_ENABLE_AI_KERNEL_TUNING +static std::vector GetKernelAsTokens(const std::string& kernel) +{ + std::vector tokens; + std::string token; + std::istringstream tokenStream( + kernel.substr(kernel.find('<') + 1, kernel.find('>') - kernel.find('<') - 1)); + while(std::getline(tokenStream, token, ',')) + { + token.erase(remove_if(token.begin(), token.end(), isspace), + token.end()); // strip whitespace + tokens.push_back(token); + } + return tokens; +} + +void PerformanceConfigHipImplicitGemmGroupWrwXdlops::InitHeuristicKernelIDs() +{ + for(int i = 0; i < valid_kernels.size(); i++) + { + if(valid_kernels[i].find("DeviceGroupedConvBwdWeight_Xdl_CShuffle") != std::string::npos) + { + heuristic_indexes.push_back(i); + heuristic_kernels.push_back(GetKernelAsTokens(valid_kernels[i])); + } + } +} + +bool PerformanceConfigHipImplicitGemmGroupWrwXdlops::ModelApplyToken(int idx, std::string value) +{ + if(idx == 13) + idx += 1; // skip + + auto eraseBegin = std::remove_if( + heuristic_indexes.begin(), heuristic_indexes.end(), [&](int heuristic_index) { + return heuristic_kernels[heuristic_index][idx] != value; + }); + + if(eraseBegin != heuristic_indexes.begin()) + { + heuristic_indexes.erase(eraseBegin, heuristic_indexes.end()); + return true; + } + return false; +} + +static std::vector GetFeatures(const ProblemDescription& problem) +{ + std::size_t n = 18; + std::vector features(n * n, 0.0f); + features[0] = 1.0; + features[n + 1] = problem.GetOutChannels(); + features[2 * n + 2] = problem.GetOutHeight(); + features[3 * n + 3] = problem.GetOutWidth(); + features[4 * n + 4] = problem.GetInChannels(); + features[5 * n + 5] = problem.GetInHeight(); + features[6 * n + 6] = problem.GetInWidth(); + features[7 * n + 7] = problem.GetWeightsHeight(); + features[8 * n + 8] = problem.GetWeightsWidth(); + features[9 * n + 9] = problem.GetPadH(); + features[10 * n + 10] = problem.GetPadW(); + features[11 * n + 11] = problem.GetKernelStrideH(); + features[12 * n + 12] = problem.GetKernelStrideW(); + features[13 * n + 13] = problem.GetDilationH(); + features[14 * n + 14] = problem.GetDilationW(); + features[15 * n + 15] = problem.GetBatchSize(); + features[16 * n + 16] = problem.GetInDataType() == miopenFloat ? 2.0 : 1.0; + features[17 * n + 17] = problem.GetGroupCount(); + return features; +} + +template +bool PerformanceConfigHipImplicitGemmGroupWrwXdlops::RunParameterPredictionModel( + const ExecutionContext& ctx, const ProblemDescription& problem) +{ + valid_kernels = FillValidKernelsIDs, CKArgs>( + problem); // filter valid_kernel ID's + InitHeuristicKernelIDs(); + static const std::string& arch = ctx.GetStream().GetDeviceName(); + static const std::string solver = "ConvHipIgemmGroupXdlops"; + std::vector features = GetFeatures(problem); + if(ai::tuning::ModelSetParams( + arch, solver, problem.GetDirection(), features, true, [&](int idx, std::string value) { + return this->ModelApplyToken(idx, value); + })) + { + index = heuristic_indexes[0]; + kernel_id = valid_kernels[index]; + MIOPEN_LOG_I("Params set by AI: " << ToString()); + return true; + } + return false; +} +#endif // MIOPEN_ENABLE_AI_KERNEL_TUNING +#endif // MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL + +bool PerformanceConfigHipImplicitGemmGroupWrwXdlops::IsModelApplicable( + const ExecutionContext& ctx, const ProblemDescription& problem) const +{ + if(ctx.GetStream().GetDeviceName() != "gfx90a" && ctx.GetStream().GetDeviceName() != "gfx942") + return false; + if(problem.GetInDataType() != miopenFloat && problem.GetInDataType() != miopenHalf) + return false; + if(miopen::IsDisabled(ENV(MIOPEN_DEBUG_GROUP_CONV_IMPLICIT_GEMM_HIP_WRW_XDLOPS_AI_HEUR))) + return false; + return true; +} void PerformanceConfigHipImplicitGemmGroupWrwXdlops::HeuristicInit( + [[maybe_unused]] const ExecutionContext& ctx, [[maybe_unused]] const ProblemDescription& problem) { + // these seem redundant index = 0; kernel_id = ""; - #if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL +#if MIOPEN_ENABLE_AI_KERNEL_TUNING + if(IsModelApplicable(ctx, problem)) + { + if(problem.GetInDataType() == miopenFloat) + { + if(RunParameterPredictionModel(ctx, problem)) + return; + } + else + { + if(RunParameterPredictionModel(ctx, problem)) + return; + } + } +#endif switch(problem.GetInDataType()) { case miopenHalf: Init(problem); break; @@ -237,9 +362,20 @@ void PerformanceConfigHipImplicitGemmGroupWrwXdlops::HeuristicInit( bool PerformanceConfigHipImplicitGemmGroupWrwXdlops::SetNextValue(const ProblemDescription& problem) { +#if MIOPEN_USE_COMPOSABLEKERNEL if(valid_kernels.empty()) { - HeuristicInit(problem); + switch(problem.GetInDataType()) + { + case miopenHalf: Init(problem); break; + case miopenFloat: Init(problem); break; + case miopenInt8: Init(problem); break; + case miopenInt32: + case miopenBFloat16: + case miopenFloat8: + case miopenBFloat8: + case miopenDouble: break; + } assert(!valid_kernels.empty()); return true; } @@ -250,6 +386,7 @@ bool PerformanceConfigHipImplicitGemmGroupWrwXdlops::SetNextValue(const ProblemD return true; } else +#endif return false; } @@ -285,10 +422,10 @@ bool PerformanceConfigHipImplicitGemmGroupWrwXdlops::operator==( PerformanceConfigHipImplicitGemmGroupWrwXdlops ConvHipImplicitGemmGroupWrwXdlops::GetDefaultPerformanceConfig( - const ExecutionContext&, const ProblemDescription& problem) const + const ExecutionContext& ctx, const ProblemDescription& problem) const { PerformanceConfigHipImplicitGemmGroupWrwXdlops pp; - pp.HeuristicInit(problem); + pp.HeuristicInit(ctx, problem); return pp; } diff --git a/src/solver/conv_ocl_dir2Dfwd_exhaustive_search.cpp b/src/solver/conv_ocl_dir2Dfwd_exhaustive_search.cpp index 9501e56f2a..b1c372bdc9 100644 --- a/src/solver/conv_ocl_dir2Dfwd_exhaustive_search.cpp +++ b/src/solver/conv_ocl_dir2Dfwd_exhaustive_search.cpp @@ -33,11 +33,7 @@ #include #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #ifdef max #undef max diff --git a/src/solver/gemm.cpp b/src/solver/gemm.cpp index 7131bf5669..64624772c5 100644 --- a/src/solver/gemm.cpp +++ b/src/solver/gemm.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -47,33 +48,6 @@ namespace conv { using ProblemDescription = miopen::conv::ProblemDescription; -#if MIOPEN_USE_GEMM -#ifdef CPPCHECK -// Keep the value unknown in cppcheck since this can differ between opencl and hip -static bool IsBf16Supported; -static bool IsFp16Supported; -#else -static constexpr const bool IsBf16Supported = MIOPEN_USE_ROCBLAS; -static constexpr const bool IsFp16Supported = MIOPEN_USE_ROCBLAS; -#endif - -static inline bool IsAnyBufferBF16(const TensorDescriptor& xDesc, - const TensorDescriptor& yDesc, - const TensorDescriptor& wDesc) -{ - return xDesc.GetType() == miopenBFloat16 || yDesc.GetType() == miopenBFloat16 || - wDesc.GetType() == miopenBFloat16; -} - -static inline bool IsAnyBufferFp16(const TensorDescriptor& xDesc, - const TensorDescriptor& yDesc, - const TensorDescriptor& wDesc) -{ - return xDesc.GetType() == miopenHalf || yDesc.GetType() == miopenHalf || - wDesc.GetType() == miopenHalf; -} -#endif - bool GemmFwdBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescription& problem) const { #if MIOPEN_USE_GEMM @@ -127,8 +101,8 @@ bool GemmFwdBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescrip return false; } return problem.IsDirectionForward() && problem.IsLayoutDefault() && - !(IsAnyBufferBF16(xDesc, yDesc, wDesc) && !IsBf16Supported) && - !(IsAnyBufferFp16(xDesc, yDesc, wDesc) && !IsFp16Supported); + !(gemm::IsAnyBufferBf16(xDesc, yDesc, wDesc) && !gemm::IsBf16Supported) && + !(gemm::IsAnyBufferFp16(xDesc, yDesc, wDesc) && !gemm::IsFp16Supported); #else std::ignore = ctx; std::ignore = problem; @@ -136,20 +110,6 @@ bool GemmFwdBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescrip #endif }; -static double -SlowdownFactor(int n_oper, const double oper_factor, const double multiple_oper_factor) -{ - if(n_oper > 0) - { - auto rv = oper_factor; - if(n_oper > 1) - rv *= multiple_oper_factor; - return rv; - } - else - return 1.0; -} - float GemmFwdBase::GetWti(const ExecutionContext&, const ProblemDescription& problem) const { decltype(auto) conv = problem.GetConv(); @@ -215,22 +175,16 @@ float GemmFwdBase::GetWti(const ExecutionContext&, const ProblemDescription& pro } auto wti = 1.0; - wti *= SlowdownFactor(n_transpose_NCHW2CNHW, 0.7, 0.9); - wti *= SlowdownFactor(n_transpose_CNHW2NCHW, 0.7, 0.9); - wti *= SlowdownFactor(n_gemm_runs, 0.9, 0.9); - wti *= SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95); - wti *= SlowdownFactor(n_transpose_packed_MN2NM, 0.7, 0.9); - wti *= SlowdownFactor(n_CastTensor, 0.95, 0.9); - wti *= SlowdownFactor(n_Im2ColGPU, 0.4, 0.8); + wti *= gemm::SlowdownFactor(n_transpose_NCHW2CNHW, 0.7, 0.9); + wti *= gemm::SlowdownFactor(n_transpose_CNHW2NCHW, 0.7, 0.9); + wti *= gemm::SlowdownFactor(n_gemm_runs, 0.9, 0.9); + wti *= gemm::SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95); + wti *= gemm::SlowdownFactor(n_transpose_packed_MN2NM, 0.7, 0.9); + wti *= gemm::SlowdownFactor(n_CastTensor, 0.95, 0.9); + wti *= gemm::SlowdownFactor(n_Im2ColGPU, 0.4, 0.8); return wti; } -// copy from convolution.cpp -// Workaround for issue 1430. -// Vega20 fails to access GPU memory larger than the return value of GetMaxMemoryAllocSize() of -// Vega10 -#define MAX_MEM_ALLOC_SZ (std::min(handle.GetMaxMemoryAllocSize(), size_t(7287183769))) - size_t GemmFwd1x1_0_2::GetWorkspaceSize(const ExecutionContext& context, const ProblemDescription& problem) const { @@ -256,9 +210,10 @@ size_t GemmFwd1x1_0_2::GetWorkspaceSize(const ExecutionContext& context, const auto y_t_size = yDesc.GetElementSize() * GetTypeSize(yDesc.GetType()); const auto gemm_trans = x_t_size + y_t_size; - if(gemm_trans > MAX_MEM_ALLOC_SZ) + if(gemm_trans > gemm::MaxMemAllocSz(handle, problem)) { - MIOPEN_LOG_I2(gemm_trans << " > " << MAX_MEM_ALLOC_SZ); + MIOPEN_LOG_I2("GemmFwd1x1_0_2:" << gemm_trans << " > " + << gemm::MaxMemAllocSz(handle, problem)); return 0; } return gemm_trans; @@ -539,9 +494,10 @@ size_t GemmFwd1x1_0_1_int8::GetWorkspaceSize(const ExecutionContext& context, std::multiplies()) * GetTypeSize(wDesc.GetType()) * conv.group_count; - if(ws_size > MAX_MEM_ALLOC_SZ) + if(ws_size > gemm::MaxMemAllocSz(handle, problem)) { - MIOPEN_LOG_I2(ws_size << " > " << MAX_MEM_ALLOC_SZ); + MIOPEN_LOG_I2("GemmFwd1x1_0_1_int8:" << ws_size << " > " + << gemm::MaxMemAllocSz(handle, problem)); return 0; } return ws_size; @@ -999,9 +955,10 @@ size_t GemmFwdRest::GetWorkspaceSize(const ExecutionContext& context, const auto ws_sz = (wDesc.GetType() == miopenInt8 ? 2 * workspace_size : workspace_size); - if(ws_sz > MAX_MEM_ALLOC_SZ) + if(ws_sz > gemm::MaxMemAllocSz(handle, problem, true)) { - MIOPEN_LOG_I2(ws_sz << " > " << MAX_MEM_ALLOC_SZ); + MIOPEN_LOG_I2("GemmFwdRest: " << ws_sz << " > " + << gemm::MaxMemAllocSz(handle, problem, true)); return 0; } return ws_sz; diff --git a/src/solver/gemm_bwd.cpp b/src/solver/gemm_bwd.cpp index e68a16d6cd..954be49119 100644 --- a/src/solver/gemm_bwd.cpp +++ b/src/solver/gemm_bwd.cpp @@ -35,65 +35,19 @@ #include #include #include +#include #include #include MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_CONV_PRECISE_ROCBLAS_TIMING) -// copy from convolution.cpp -// Workaround for issue 1430. -// Vega20 fails to access GPU memory larger than the return value of GetMaxMemoryAllocSize() of -// Vega10 -#define MAX_MEM_ALLOC_SZ (std::min(handle.GetMaxMemoryAllocSize(), size_t(7287183769))) - namespace miopen { namespace solver { namespace conv { using ProblemDescription = miopen::conv::ProblemDescription; -#if MIOPEN_USE_GEMM -#ifdef CPPCHECK -// Keep the value unknown in cppcheck since this can differ between opencl and hip -static bool IsBf16Supported; -static bool IsFp16Supported; -#else -static constexpr const bool IsBf16Supported = MIOPEN_USE_ROCBLAS; -static constexpr const bool IsFp16Supported = MIOPEN_USE_ROCBLAS; -#endif - -static inline bool IsAnyBufferBF16(const TensorDescriptor& xDesc, - const TensorDescriptor& yDesc, - const TensorDescriptor& wDesc) -{ - return xDesc.GetType() == miopenBFloat16 || yDesc.GetType() == miopenBFloat16 || - wDesc.GetType() == miopenBFloat16; -} - -static inline bool IsAnyBufferFp16(const TensorDescriptor& xDesc, - const TensorDescriptor& yDesc, - const TensorDescriptor& wDesc) -{ - return xDesc.GetType() == miopenHalf || yDesc.GetType() == miopenHalf || - wDesc.GetType() == miopenHalf; -} -#endif - -static double -SlowdownFactor(int n_oper, const double oper_factor, const double multiple_oper_factor) -{ - if(n_oper > 0) - { - auto rv = oper_factor; - if(n_oper > 1) - rv *= multiple_oper_factor; - return rv; - } - else - return 1.0; -} - bool GemmBwdBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescription& problem) const { #if MIOPEN_USE_GEMM @@ -140,8 +94,8 @@ bool GemmBwdBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescrip return false; } return problem.IsDirectionBackwardData() && problem.IsLayoutDefault() && - !(IsAnyBufferBF16(dxDesc, dyDesc, wDesc) && !IsBf16Supported) && - !(IsAnyBufferFp16(dxDesc, dyDesc, wDesc) && !IsFp16Supported); + !(gemm::IsAnyBufferBf16(dxDesc, dyDesc, wDesc) && !gemm::IsBf16Supported) && + !(gemm::IsAnyBufferFp16(dxDesc, dyDesc, wDesc) && !gemm::IsFp16Supported); #else std::ignore = ctx; std::ignore = problem; @@ -194,12 +148,12 @@ float GemmBwdBase::GetWti(const ExecutionContext&, const ProblemDescription& pro } auto wti = 1.0; - wti *= SlowdownFactor(n_SetTensor, 0.95, 0.99); - wti *= SlowdownFactor(n_transpose_NCHW2CNHW, 0.7, 0.9); - wti *= SlowdownFactor(n_transpose_CNHW2NCHW, 0.7, 0.9); - wti *= SlowdownFactor(n_gemm_runs, 0.9, 0.9); - wti *= SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95); - wti *= SlowdownFactor(n_Col2ImGPU, 0.4, 0.8); + wti *= gemm::SlowdownFactor(n_SetTensor, 0.95, 0.99); + wti *= gemm::SlowdownFactor(n_transpose_NCHW2CNHW, 0.7, 0.9); + wti *= gemm::SlowdownFactor(n_transpose_CNHW2NCHW, 0.7, 0.9); + wti *= gemm::SlowdownFactor(n_gemm_runs, 0.9, 0.9); + wti *= gemm::SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95); + wti *= gemm::SlowdownFactor(n_Col2ImGPU, 0.4, 0.8); return wti; } @@ -228,9 +182,10 @@ size_t GemmBwd1x1_stride2::GetWorkspaceSize(const ExecutionContext& context, const auto dy_t_size = dyDesc.GetElementSize() * GetTypeSize(dyDesc.GetType()); const auto gemm_trans = dx_t_size + dy_t_size; - if(gemm_trans > MAX_MEM_ALLOC_SZ) + if(gemm_trans > gemm::MaxMemAllocSz(handle, problem)) { - MIOPEN_LOG_I2(gemm_trans << " > " << MAX_MEM_ALLOC_SZ); + MIOPEN_LOG_I2("GemmBwd1x1_stride2: " << gemm_trans << " > " + << gemm::MaxMemAllocSz(handle, problem)); return 0; } return gemm_trans; @@ -659,9 +614,10 @@ size_t GemmBwdRest::GetWorkspaceSize(const ExecutionContext& context, std::multiplies()) * GetTypeSize(dyDesc.GetType()) * conv.group_count; - if(gemm_size > MAX_MEM_ALLOC_SZ) + if(gemm_size > gemm::MaxMemAllocSz(handle, problem, true)) { - MIOPEN_LOG_I2(gemm_size << " > " << MAX_MEM_ALLOC_SZ); + MIOPEN_LOG_I2("GemmBwdRest: " << gemm_size << " > " + << gemm::MaxMemAllocSz(handle, problem, true)); return 0; } return gemm_size; diff --git a/src/solver/gemm_common.cpp b/src/solver/gemm_common.cpp new file mode 100644 index 0000000000..b468b83d8d --- /dev/null +++ b/src/solver/gemm_common.cpp @@ -0,0 +1,107 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +// Workaround for MLOpen issue 1430. Vega20 fails to access GPU memory +// larger than the return value of GetMaxMemoryAllocSize() of Vega10. +// Due to historical reasons, this W/A is applied to all targets. +// We are going to keep it as is until the new GEMM backend +// is used instead of rocBLAS. See also issue #2809. +#define WORKAROUND_MLOPEN_ISSUE_1430 1 + +// Double the limit for FP32, for GemmFwdRest and GemmBwdRest solvers only. +// See issues #2789 and #2808. +// +// IMPORTANT: The limit can only be increased for the "rest-of" kind GEMM solvers, +// since there are dependencies between the applicability of GEMM solvers. +// For example, expanding the applicability of GemmFwd1x1_0_1 will result +// in narrowing the applicability of GemmFwdRest. This side effect will +// lead to errors unless the databases are re-tuned. +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_WORKAROUND_ISSUE_2789) + +namespace miopen { +namespace solver { +namespace conv { +namespace gemm { + +std::size_t MaxMemAllocSz(Handle& h, + const miopen::conv::ProblemDescription& problem, + bool double_limit_for_fp32) +{ + const auto m = h.GetMaxMemoryAllocSize(); +#if WORKAROUND_MLOPEN_ISSUE_1430 + auto limit = static_cast(7287183769); + if(!miopen::IsDisabled(ENV(MIOPEN_WORKAROUND_ISSUE_2789))) + { + if(problem.IsFp32() && double_limit_for_fp32) + limit *= 2; + } + return std::min(m, limit); +#else + return m; +#endif +} + +bool IsAnyBufferBf16(const TensorDescriptor& xDesc, + const TensorDescriptor& yDesc, + const TensorDescriptor& wDesc) +{ + return xDesc.GetType() == miopenBFloat16 // + || yDesc.GetType() == miopenBFloat16 // + || wDesc.GetType() == miopenBFloat16; +} + +bool IsAnyBufferFp16(const TensorDescriptor& xDesc, + const TensorDescriptor& yDesc, + const TensorDescriptor& wDesc) +{ + return xDesc.GetType() == miopenHalf // + || yDesc.GetType() == miopenHalf // + || wDesc.GetType() == miopenHalf; +} + +double SlowdownFactor(const int n_oper, const double oper_factor, const double multiple_oper_factor) +{ + if(n_oper > 0) + { + auto rv = oper_factor; + if(n_oper > 1) + rv *= multiple_oper_factor; + return rv; + } + else + return 1.0; +} + +} // namespace gemm +} // namespace conv +} // namespace solver +} // namespace miopen diff --git a/src/solver/gemm_wrw.cpp b/src/solver/gemm_wrw.cpp index b07a9200d5..ec3e603582 100644 --- a/src/solver/gemm_wrw.cpp +++ b/src/solver/gemm_wrw.cpp @@ -1,8 +1,35 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 +#include #include #include @@ -10,59 +37,12 @@ MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_CONV_PRECISE_ROCBLAS_TIMING) -// copy from convolution.cpp -// Workaround for issue 1430. -// Vega20 fails to access GPU memory larger than the return value of GetMaxMemoryAllocSize() of -// Vega10 -#define MAX_MEM_ALLOC_SZ (std::min(handle.GetMaxMemoryAllocSize(), size_t(7287183769))) - namespace miopen { namespace solver { namespace conv { using ProblemDescription = miopen::conv::ProblemDescription; -#if MIOPEN_USE_GEMM -#ifdef CPPCHECK -// Keep the value unknown in cppcheck since this can differ between opencl and hip -static bool IsBF16PathValid; -static bool IsFp16Supported; -#else -static const bool IsBF16PathValid = MIOPEN_USE_ROCBLAS; -static const bool IsFp16Supported = MIOPEN_USE_ROCBLAS; -#endif - -static inline bool IsAnyBufferBF16(const TensorDescriptor& xDesc, - const TensorDescriptor& yDesc, - const TensorDescriptor& wDesc) -{ - return xDesc.GetType() == miopenBFloat16 || yDesc.GetType() == miopenBFloat16 || - wDesc.GetType() == miopenBFloat16; -} - -static inline bool IsAnyBufferFp16(const TensorDescriptor& xDesc, - const TensorDescriptor& yDesc, - const TensorDescriptor& wDesc) -{ - return xDesc.GetType() == miopenHalf || yDesc.GetType() == miopenHalf || - wDesc.GetType() == miopenHalf; -} - -static double -SlowdownFactor(int n_oper, const double oper_factor, const double multiple_oper_factor) -{ - if(n_oper > 0) - { - auto rv = oper_factor; - if(n_oper > 1) - rv *= multiple_oper_factor; - return rv; - } - else - return 1.0; -} -#endif - bool GemmWrwBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescription& problem) const { #if MIOPEN_USE_GEMM @@ -109,8 +89,8 @@ bool GemmWrwBase::IsApplicable(const ExecutionContext& ctx, const ProblemDescrip return false; } return problem.IsDirectionBackwardWrW() && problem.IsLayoutDefault() && - !(IsAnyBufferBF16(xDesc, dyDesc, dwDesc) && !IsBF16PathValid) && - !(IsAnyBufferFp16(xDesc, dyDesc, dwDesc) && !IsFp16Supported); + !(gemm::IsAnyBufferBf16(xDesc, dyDesc, dwDesc) && !gemm::IsBf16Supported) && + !(gemm::IsAnyBufferFp16(xDesc, dyDesc, dwDesc) && !gemm::IsFp16Supported); #else std::ignore = ctx; std::ignore = problem; @@ -154,10 +134,10 @@ float GemmWrwBase::GetWti(const ExecutionContext&, const ProblemDescription& pro } auto wti = 0.7; // Memory overhead for WrW is bigger then for Fwd/Bwd. - wti *= SlowdownFactor(n_gemm_runs, 0.9, 0.9); - wti *= SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95); - wti *= SlowdownFactor(n_gemm_strided_batched_sequental, 1.0, 0.9); - wti *= SlowdownFactor(n_Im2ColGPU, 0.4, 0.8); + wti *= gemm::SlowdownFactor(n_gemm_runs, 0.9, 0.9); + wti *= gemm::SlowdownFactor(n_gemm_strided_batched, 1.0, 0.95); + wti *= gemm::SlowdownFactor(n_gemm_strided_batched_sequental, 1.0, 0.9); + wti *= gemm::SlowdownFactor(n_Im2ColGPU, 0.4, 0.8); return wti; #else std::ignore = problem; @@ -376,9 +356,12 @@ size_t GemmWrwUniversal::GetWorkspaceSize(const ExecutionContext& context, std::multiplies()) * conv.group_count; - if(ws_size > MAX_MEM_ALLOC_SZ) + if(ws_size > gemm::MaxMemAllocSz(handle, problem)) + { + MIOPEN_LOG_I2("GemmWrwUniversal: " << ws_size << " > " + << gemm::MaxMemAllocSz(handle, problem)); return 0; - + } return ws_size; #else std::ignore = context; diff --git a/src/solver/mha/mha_solver_backward.cpp b/src/solver/mha/mha_solver_backward.cpp new file mode 100644 index 0000000000..8938541bf6 --- /dev/null +++ b/src/solver/mha/mha_solver_backward.cpp @@ -0,0 +1,452 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 +#include +#include +#include + +#include +#include +#include + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_ATTN_NAIVE_BWD) + +namespace miopen { + +namespace solver { + +namespace mha { + +namespace { // TODO: Issue #2748 +template , T>> +constexpr inline S Ceil(const T val, const T div) +{ + return (val - 1 + div) / div; +} + +template , T>> +constexpr S RoundUpToMultiple(T val, T mul) +{ + return Ceil(val, mul) * mul; +} + +template +constexpr T nextPow2(T v) +{ + static_assert(std::is_unsigned_v); + + if(v == 1) + { + return (v << 1); + } + else + { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + if constexpr(sizeof(T) > 1) + v |= v >> 8; + if constexpr(sizeof(T) > 2) + v |= v >> 16; + if constexpr(sizeof(T) > 4) + v |= v >> 32; + v++; + return v; + } +} + +MultiBufferWorkspaceTraits SplitBufferToWorkspace(size_t S, size_t D, size_t NHS) +{ + // the first MatMuls (N*H*S*D) * (N*H*S*D)T = (N*H*S*S) + // the second MatMuls (N*H*S*S)[T] * (N*H*S*D) = (N*H*S*D) + // dOxO row reduction (N*H*S*1) + return MultiBufferWorkspaceTraits{NHS * S * get_data_size(miopenFloat), // first matmuls + NHS * S * get_data_size(miopenFloat), // first matmuls + NHS * std::max(S, D) * + get_data_size(miopenFloat), // reduction and second matmul + NHS * D * get_data_size(miopenFloat), // second matmuls + NHS * D * get_data_size(miopenFloat)}; // second matmuls +} + +MultiBufferWorkspaceTraits SplitBufferToWorkspace(const std::vector& lengths) +{ + const auto [N, H, S, D] = miopen::tien<4>(lengths); + return SplitBufferToWorkspace(S, D, N * H * S); +} + +miopen::HipEventPtr make_hip_fast_event() +{ + hipEvent_t result = nullptr; + hipEventCreateWithFlags(&result, hipEventDisableTiming); + return miopen::HipEventPtr{result}; +} +} // namespace + +bool MhaBackward::IsApplicable([[maybe_unused]] const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const +{ + // It's important to have this check before problem.GetDescsBackward() call + if(problem.IsForward()) + { + return false; + } + + const miopen::mha::MhaInputDescsBackward& descsBackward = problem.GetDescsBackward(); + + auto [N, H, S, D] = miopen::tien<4>(descsBackward.kDesc.GetLengths()); + + return !miopen::IsDisabled(ENV(MIOPEN_DEBUG_ATTN_NAIVE_BWD)) // + && S <= std::numeric_limits::max() // + && D <= std::numeric_limits::max() // + && descsBackward.kDesc.IsPacked() // + && descsBackward.qDesc.IsPacked() // + && descsBackward.vDesc.IsPacked() // + && descsBackward.oDesc.IsPacked() // + && descsBackward.doDesc.IsPacked() // + && descsBackward.mDesc.IsPacked() // + && descsBackward.zInvDesc.IsPacked() // + && descsBackward.dkDesc.IsPacked() // + && descsBackward.dqDesc.IsPacked() // + && descsBackward.dvDesc.IsPacked() // + && MIOPEN_USE_GEMM; +} + +std::size_t MhaBackward::GetWorkspaceSize([[maybe_unused]] const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const +{ + return SplitBufferToWorkspace(problem.GetDescsBackward().kDesc.GetLengths()).GetSize(); +} + +ConvSolution MhaBackward::GetSolution(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const +{ + auto result = ConvSolution{miopenStatusSuccess}; + + auto [N, H, S, D] = miopen::tien<4>(problem.GetDescsBackward().kDesc.GetLengths()); + uint32_t emb_dim = static_cast(D); + uint32_t seq_len = static_cast(S); + uint64_t nhs = N * H * S; + uint64_t nhsd = N * H * S * D; + float scale = problem.GetDescsBackward().scale; // just to capture it into lambda + + auto warpSize = context.GetStream().GetWavefrontWidth(); + + size_t local_threads = std::clamp(nextPow2(D), warpSize, static_cast(256)); + size_t global_threads = nhs * local_threads; + + auto dOxO_reduction_kernel = KernelInfo{}; + dOxO_reduction_kernel.comp_options = + KernelBuildParameters{{"THREADS", local_threads}}.GenerateFor(kbp::HIP{}); + dOxO_reduction_kernel.kernel_file = "MIOpenSoftmaxAttn.cpp"; + dOxO_reduction_kernel.kernel_name = D > local_threads ? "ScaleRowReduceCommon" + : D > warpSize ? "ScaleRowReduceBlock" + : "ScaleRowReduceWarp"; + if(D <= warpSize) + { + global_threads = Ceil(global_threads, local_threads / warpSize); + } + dOxO_reduction_kernel.l_wk = {local_threads, 1, 1}; + dOxO_reduction_kernel.g_wk = {global_threads, 1, 1}; + result.construction_params.push_back(dOxO_reduction_kernel); + + local_threads = std::clamp(nextPow2(S), warpSize, static_cast(256)); + global_threads = nhs * local_threads; + + auto bwd_attention_kernel = KernelInfo{}; + bwd_attention_kernel.comp_options = + KernelBuildParameters{{"THREADS", local_threads}}.GenerateFor(kbp::HIP{}); + bwd_attention_kernel.kernel_file = "MIOpenSoftmaxAttn.cpp"; + bwd_attention_kernel.kernel_name = S > local_threads ? "BwdAttentionCommon" + : S > warpSize ? "BwdAttentionBlock" + : "BwdAttentionWarp"; + if(S <= warpSize) + { + global_threads = Ceil(global_threads, local_threads / warpSize); + } + bwd_attention_kernel.l_wk = {local_threads, 1, 1}; + bwd_attention_kernel.g_wk = {global_threads, 1, 1}; + result.construction_params.push_back(bwd_attention_kernel); + + auto getBuffPart = [ws = SplitBufferToWorkspace(S, D, nhs)](void* buffer, size_t part_idx) { + return static_cast(static_cast(buffer) + ws.GetOffset(part_idx)); + }; + + local_threads = std::clamp(nextPow2(nhsd), warpSize, static_cast(256)); + global_threads = RoundUpToMultiple(nhsd, local_threads); + + auto scale_reduce_kernel = KernelInfo{}; + scale_reduce_kernel.comp_options = + KernelBuildParameters{{"THREADS", local_threads}}.GenerateFor(kbp::HIP{}); + scale_reduce_kernel.kernel_file = "MIOpenSoftmaxAttn.cpp"; + scale_reduce_kernel.kernel_name = "ScaleReduce"; + scale_reduce_kernel.l_wk = {local_threads, 1, 1}; + scale_reduce_kernel.g_wk = {global_threads, 1, 1}; + result.construction_params.push_back(scale_reduce_kernel); + +#if MIOPEN_USE_GEMM + GemmDescriptor QK_desc(false, + false, + true, + S, + S, + D, + D, + D, + S, + N * H, + S * D, + S * D, + S * S, + problem.GetDescsBackward().scale, + 0.0f, + problem.GetDescsBackward().kDesc.GetType(), + true); + + GemmDescriptor dOV_desc(false, + false, + true, + S, + S, + D, + D, + D, + S, + N * H, + S * D, + S * D, + S * S, + 1.0f, + 0.0f, + problem.GetDescsBackward().vDesc.GetType(), + true); + + GemmDescriptor xK_desc(false, + false, + false, + S, + D, + S, + S, + D, + D, + N * H, + S * S, + S * D, + S * D, + 1.0f, + 0.0f, + problem.GetDescsBackward().kDesc.GetType(), + true); + + GemmDescriptor xQdO_desc(false, + true, + false, + S, + D, + S, + S, + D, + D, + N * H, + S * S, + S * D, + S * D, + 1.0f, + 0.0f, + problem.GetDescsBackward().oDesc.GetType(), + true); +#endif + + result.invoker_factory = [=](const std::vector& kernels) { + return [=](const Handle& handle_, const AnyInvokeParams& raw_params) { + decltype(auto) params = raw_params.CastTo(); + + HipEventPtr start = nullptr; + HipEventPtr stop = nullptr; + const bool profiling = handle_.IsProfilingEnabled(); + const auto& dataBwd = params.GetDataBackward(); + + handle_.ReserveExtraStreamsInPool(2); + + auto recordSyncEvent = [&handle_]() { + auto event = make_hip_fast_event(); + hipEventRecord(event.get(), handle_.GetStream()); + return event; + }; + + auto waitSyncEvent = [&handle_](HipEventPtr&& event) { + auto tmp_for_deletion(std::move(event)); + hipStreamWaitEvent(handle_.GetStream(), tmp_for_deletion.get(), 0); + }; + + if(profiling) + { + start = make_hip_event(); + stop = make_hip_event(); + handle_.EnableProfiling(false); + hipEventRecord(start.get(), handle_.GetStream()); + } + + void* fp32_QxK_S_ws = getBuffPart(params.GetWorkspace(), 0); + void* fp32_dOxV_dS_ws = getBuffPart(params.GetWorkspace(), 1); + void* fp32_dOxO_SxdO_ws = getBuffPart(params.GetWorkspace(), 2); + void* fp32_dSxK_ws = getBuffPart(params.GetWorkspace(), 3); + void* fp32_dSxQ_ws = getBuffPart(params.GetWorkspace(), 4); + + decltype(auto) dOxO_reduction_kernel = handle_.Run(kernels[0]); + dOxO_reduction_kernel(dataBwd.doData, + dataBwd.oData, + fp32_dOxO_SxdO_ws, + dataBwd.descaleDOData, + dataBwd.descaleOData, + dataBwd.dropoutProbabilityData, + emb_dim, + nhs); + hipMemsetAsync(dataBwd.amaxDSData, 0, sizeof(float), handle_.GetStream()); + hipMemsetAsync(dataBwd.amaxDVData, 0, sizeof(float), handle_.GetStream()); + + handle_.SetStreamFromPool(1); +#if MIOPEN_USE_GEMM + CallGemmStridedBatched( + handle_, QK_desc, dataBwd.qData, 0, dataBwd.kData, 0, fp32_QxK_S_ws, 0); +#endif + HipEventPtr event_QxK = recordSyncEvent(); + hipMemsetAsync(dataBwd.amaxDQData, 0, sizeof(float), handle_.GetStream()); + + handle_.SetStreamFromPool(2); +#if MIOPEN_USE_GEMM + CallGemmStridedBatched( + handle_, dOV_desc, dataBwd.doData, 0, dataBwd.vData, 0, fp32_dOxV_dS_ws, 0); +#endif + HipEventPtr event_dOxV = recordSyncEvent(); + hipMemsetAsync(dataBwd.amaxDKData, 0, sizeof(float), handle_.GetStream()); + + handle_.SetStreamFromPool(0); + waitSyncEvent(std::move(event_QxK)); + waitSyncEvent(std::move(event_dOxV)); + + decltype(auto) bwd_attention_kernel = handle_.Run(kernels[1]); + bwd_attention_kernel(fp32_QxK_S_ws, + fp32_dOxV_dS_ws, + dataBwd.mData, + dataBwd.zInvData, + fp32_dOxO_SxdO_ws, + dataBwd.amaxDSData, + dataBwd.descaleQData, + dataBwd.descaleKData, + dataBwd.descaleDOData, + dataBwd.descaleVData, + dataBwd.scaleSData, + dataBwd.scaleDSData, + dataBwd.dropoutSeedData, + dataBwd.dropoutOffsetData, + dataBwd.dropoutProbabilityData, + scale, + seq_len, + nhs); + +#if MIOPEN_USE_GEMM + CallGemmStridedBatched( + handle_, xK_desc, fp32_dOxV_dS_ws, 0, dataBwd.kData, 0, fp32_dSxK_ws, 0); +#endif + HipEventPtr event_bwd1 = recordSyncEvent(); + +#if MIOPEN_USE_GEMM + CallGemmStridedBatched( + handle_, xQdO_desc, fp32_dOxV_dS_ws, 0, dataBwd.qData, 0, fp32_dSxQ_ws, 0); +#endif + HipEventPtr event_bwd2 = recordSyncEvent(); + + decltype(auto) scale_reduce_kernel = handle_.Run(kernels[2]); + + handle_.SetStreamFromPool(1); + waitSyncEvent(std::move(event_bwd1)); + scale_reduce_kernel(fp32_dSxK_ws, + dataBwd.dqData, + dataBwd.amaxDQData, + dataBwd.descaleDSData, + dataBwd.descaleKData, + dataBwd.scaleDQData, + nhsd); + HipEventPtr event_bwd3 = recordSyncEvent(); + + handle_.SetStreamFromPool(2); + waitSyncEvent(std::move(event_bwd2)); + scale_reduce_kernel(fp32_dSxQ_ws, + dataBwd.dkData, + dataBwd.amaxDKData, + dataBwd.descaleDSData, + dataBwd.descaleQData, + dataBwd.scaleDKData, + nhsd); + HipEventPtr event_bwd4 = recordSyncEvent(); + + handle_.SetStreamFromPool(0); +#if MIOPEN_USE_GEMM + CallGemmStridedBatched( + handle_, xQdO_desc, fp32_QxK_S_ws, 0, dataBwd.doData, 0, fp32_dOxO_SxdO_ws, 0); +#endif + + scale_reduce_kernel(fp32_dOxO_SxdO_ws, + dataBwd.dvData, + dataBwd.amaxDVData, + dataBwd.descaleSData, + dataBwd.descaleDOData, + dataBwd.scaleDVData, + nhsd); + + waitSyncEvent(std::move(event_bwd3)); + waitSyncEvent(std::move(event_bwd4)); + + if(profiling) + { + hipEventRecord(stop.get(), handle_.GetStream()); + handle_.EnableProfiling(true); + hipEventSynchronize(stop.get()); + float mS = 0; + hipEventElapsedTime(&mS, start.get(), stop.get()); + handle_.ResetKernelTime(); + handle_.AccumKernelTime(mS); + } + }; + }; + + return result; +} + +bool MhaBackward::MayNeedWorkspace() const { return true; } + +} // namespace mha + +} // namespace solver + +} // namespace miopen diff --git a/src/solver/mha/mha_solver_forward.cpp b/src/solver/mha/mha_solver_forward.cpp new file mode 100644 index 0000000000..b685a40f55 --- /dev/null +++ b/src/solver/mha/mha_solver_forward.cpp @@ -0,0 +1,295 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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 +#include +#include +#include + +#include +#include +#include + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_ATTN_NAIVE_FWD) + +namespace miopen { + +namespace solver { + +namespace mha { + +namespace { // TODO: Issue #2748 +template , T>> +constexpr inline S Ceil(const T val, const T div) +{ + return (val - 1 + div) / div; +} + +template , T>> +constexpr S RoundUpToMultiple(T val, T mul) +{ + return Ceil(val, mul) * mul; +} + +template +constexpr T nextPow2(T v) +{ + static_assert(std::is_unsigned_v); + + if(v == 1) + { + return (v << 1); + } + else + { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + if constexpr(sizeof(T) > 1) + v |= v >> 8; + if constexpr(sizeof(T) > 2) + v |= v >> 16; + if constexpr(sizeof(T) > 4) + v |= v >> 32; + v++; + return v; + } +} + +MultiBufferWorkspaceTraits SplitBufferToWorkspace(size_t S, size_t D, size_t NHS) +{ + // the first MatMul (N*H*S*D) * (N*H*S*D)T = (N*H*S*S) + // the second MatMul (N*H*S*S) * (N*H*S*D) = (N*H*S*D) + return MultiBufferWorkspaceTraits{ + NHS * std::max(S, D) * get_data_size(miopenFloat), // first and second matmuls tensor + NHS * S * get_data_size(miopenFloat)}; // first matmul tensor +} + +MultiBufferWorkspaceTraits SplitBufferToWorkspace(const std::vector& lengths) +{ + const auto [N, H, S, D] = miopen::tien<4>(lengths); + return SplitBufferToWorkspace(S, D, N * H * S); +} +} // namespace + +bool MhaForward::IsApplicable([[maybe_unused]] const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const +{ + // It's important to have this check before problem.GetDescsForward() call + if(!problem.IsForward()) + { + return false; + } + + const miopen::mha::MhaInputDescsForward& descsForward = problem.GetDescsForward(); + + auto [N, H, S, D] = miopen::tien<4>(descsForward.kDesc.GetLengths()); + + return !miopen::IsDisabled(ENV(MIOPEN_DEBUG_ATTN_NAIVE_FWD)) // + && S <= std::numeric_limits::max() // + && descsForward.kDesc.IsPacked() // + && descsForward.qDesc.IsPacked() // + && descsForward.vDesc.IsPacked() // + && descsForward.oDesc.IsPacked() // + && descsForward.mDesc.IsPacked() // + && descsForward.zInvDesc.IsPacked() // + && MIOPEN_USE_GEMM; +} + +std::size_t MhaForward::GetWorkspaceSize([[maybe_unused]] const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const +{ + return SplitBufferToWorkspace(problem.GetDescsForward().kDesc.GetLengths()).GetSize(); +} + +ConvSolution MhaForward::GetSolution(const ExecutionContext& context, + const miopen::mha::ProblemDescription& problem) const +{ + auto result = ConvSolution{miopenStatusSuccess}; + + auto [N, H, S, D] = miopen::tien<4>(problem.GetDescsForward().kDesc.GetLengths()); + uint32_t seq_len = static_cast(S); + uint64_t nhs = N * H * S; + uint64_t nhsd = N * H * S * D; + + auto warpSize = context.GetStream().GetWavefrontWidth(); + + size_t local_threads = std::clamp(nextPow2(S), warpSize, static_cast(256)); + size_t global_threads = nhs * local_threads; + + auto softmax_kernel = KernelInfo{}; + softmax_kernel.comp_options = + KernelBuildParameters{{"THREADS", local_threads}}.GenerateFor(kbp::HIP{}); + softmax_kernel.kernel_file = "MIOpenSoftmaxAttn.cpp"; + softmax_kernel.kernel_name = S > local_threads ? "SoftMaxCommon" + : S > warpSize ? "SoftMaxBlock" + : "SoftMaxWarp"; + if(S <= warpSize) + { + global_threads = Ceil(global_threads, local_threads / warpSize); + } + softmax_kernel.l_wk = {local_threads, 1, 1}; + softmax_kernel.g_wk = {global_threads, 1, 1}; + result.construction_params.push_back(softmax_kernel); + + auto getBuffPart = [ws = SplitBufferToWorkspace(S, D, nhs)](void* buffer, size_t part_idx) { + return static_cast(static_cast(buffer) + ws.GetOffset(part_idx)); + }; + + local_threads = std::clamp(nextPow2(nhsd), warpSize, static_cast(256)); + global_threads = RoundUpToMultiple(nhsd, local_threads); + + auto scale_reduce_kernel = KernelInfo{}; + scale_reduce_kernel.comp_options = + KernelBuildParameters{{"THREADS", local_threads}}.GenerateFor(kbp::HIP{}); + scale_reduce_kernel.kernel_file = "MIOpenSoftmaxAttn.cpp"; + scale_reduce_kernel.kernel_name = "ScaleReduce"; + scale_reduce_kernel.l_wk = {local_threads, 1, 1}; + scale_reduce_kernel.g_wk = {global_threads, 1, 1}; + result.construction_params.push_back(scale_reduce_kernel); + +#if MIOPEN_USE_GEMM + GemmDescriptor QK_desc(false, + false, + true, + S, + S, + D, + D, + D, + S, + N * H, + S * D, + S * D, + S * S, + problem.GetDescsForward().scale, + 0.0f, + problem.GetDescsForward().kDesc.GetType(), + true); + + GemmDescriptor SV_desc(false, + false, + false, + S, + D, + S, + S, + D, + D, + N * H, + S * S, + S * D, + S * D, + 1.0f, + 0.0f, + problem.GetDescsForward().vDesc.GetType(), + true); +#endif + + result.invoker_factory = [=](const std::vector& kernels) { + return [=](const Handle& handle_, const AnyInvokeParams& raw_params) { + decltype(auto) params = raw_params.CastTo(); + + HipEventPtr start = nullptr; + HipEventPtr stop = nullptr; + const bool profiling = handle_.IsProfilingEnabled(); + const auto& dataFwd = params.GetDataForward(); + + if(profiling) + { + start = make_hip_event(); + stop = make_hip_event(); + handle_.EnableProfiling(false); + hipEventRecord(start.get(), handle_.GetStream()); + } + + // zero amax output data to use atomics + hipMemsetAsync(dataFwd.amaxSData, 0, sizeof(float), handle_.GetStream()); + hipMemsetAsync(dataFwd.amaxOData, 0, sizeof(float), handle_.GetStream()); + + void* fp32_ws = getBuffPart(params.GetWorkspace(), 0); + void* fp8_ws = getBuffPart(params.GetWorkspace(), 1); + +#if MIOPEN_USE_GEMM + CallGemmStridedBatched( + handle_, QK_desc, dataFwd.qData, 0, dataFwd.kData, 0, fp32_ws, 0); +#endif + decltype(auto) softmax_kernel = handle_.Run(kernels.front()); + softmax_kernel(fp32_ws, + fp8_ws, + dataFwd.mData, + dataFwd.zInvData, + dataFwd.amaxSData, + dataFwd.descaleQData, + dataFwd.descaleKData, + dataFwd.scaleSData, + dataFwd.dropoutSeedData, + dataFwd.dropoutOffsetData, + dataFwd.dropoutProbabilityData, + seq_len, + nhs); + +#if MIOPEN_USE_GEMM + CallGemmStridedBatched(handle_, SV_desc, fp8_ws, 0, dataFwd.vData, 0, fp32_ws, 0); +#endif + + decltype(auto) scale_reduce_kernel = handle_.Run(kernels.back()); + scale_reduce_kernel(fp32_ws, + dataFwd.oData, + dataFwd.amaxOData, + dataFwd.descaleSData, + dataFwd.descaleVData, + dataFwd.scaleOData, + nhsd); + + if(profiling) + { + hipEventRecord(stop.get(), handle_.GetStream()); + handle_.EnableProfiling(true); + hipEventSynchronize(stop.get()); + float mS = 0; + hipEventElapsedTime(&mS, start.get(), stop.get()); + handle_.ResetKernelTime(); + handle_.AccumKernelTime(mS); + } + }; + }; + + return result; +} + +bool MhaForward::MayNeedWorkspace() const { return true; } + +} // namespace mha + +} // namespace solver + +} // namespace miopen diff --git a/src/solver/pooling/backward2d.cpp b/src/solver/pooling/backward2d.cpp index e865ddb4e8..0278e9bd54 100644 --- a/src/solver/pooling/backward2d.cpp +++ b/src/solver/pooling/backward2d.cpp @@ -174,7 +174,7 @@ bool PoolingBackward2d::IsApplicable(const ExecutionContext&, (problem.GetPooling().GetMode() == miopenPoolingMax || problem.GetPooling().GetMode() == miopenPoolingAverage || problem.GetPooling().GetMode() == miopenPoolingAverageInclusive) && - problem.GetXDesc().GetSize() == 4 && problem.GetXDesc().GetLayout("NCHW") == "NCHW" && + problem.GetXDesc().GetNumDims() == 4 && problem.GetXDesc().GetLayout("NCHW") == "NCHW" && problem.GetYDesc().GetLayout("NCHW") == "NCHW" && sizeof_local_memory(problem) <= TargetProperties::GetMaxLocalMemorySize(); } diff --git a/src/solver/pooling/backwardNd.cpp b/src/solver/pooling/backwardNd.cpp index 4095dfe14d..3790c5de84 100644 --- a/src/solver/pooling/backwardNd.cpp +++ b/src/solver/pooling/backwardNd.cpp @@ -50,11 +50,11 @@ bool PoolingBackwardNd::IsApplicable(const ExecutionContext&, || problem.GetPooling().GetMode() == miopenPoolingAverage // || problem.GetPooling().GetMode() == miopenPoolingAverageInclusive) // && ( // - (problem.GetXDesc().GetSize() == 5 // + (problem.GetXDesc().GetNumDims() == 5 // && problem.GetXDesc().GetLayout("NCDHW") == "NCDHW" // && problem.GetYDesc().GetLayout("NCDHW") == "NCDHW") // || // - (problem.GetXDesc().GetSize() == 4 // + (problem.GetXDesc().GetNumDims() == 4 // && problem.GetXDesc().GetLayout("NCHW") == "NCHW" // && problem.GetYDesc().GetLayout("NCHW") == "NCHW") // ) // @@ -102,7 +102,7 @@ PoolingBackwardNd::GetSolution(const ExecutionContext&, int batch = top.GetLengths()[0]; int chal = top.GetLengths()[1]; - const bool is2d = (bot.GetSize() == 4); + const bool is2d = (bot.GetNumDims() == 4); int bot_d = is2d ? 1 : *(bot.GetLengths().rbegin() + 2); int bot_h = *(bot.GetLengths().rbegin() + 1); diff --git a/src/solver/pooling/forward2d.cpp b/src/solver/pooling/forward2d.cpp index 1ba77d93dc..87fd0e851f 100644 --- a/src/solver/pooling/forward2d.cpp +++ b/src/solver/pooling/forward2d.cpp @@ -136,7 +136,7 @@ bool PoolingForward2d::IsApplicable(const ExecutionContext& context, const miopen::pooling::ProblemDescription& problem) const { return problem.GetDirection() == miopen::pooling::Direction::Forward && - problem.GetXDesc().GetSize() == 4 && + problem.GetXDesc().GetNumDims() == 4 && problem.GetXDesc().GetType() == problem.GetYDesc().GetType() && (problem.GetXDesc().GetType() == miopenFloat || problem.GetXDesc().GetType() == miopenHalf) && diff --git a/src/solver/pooling/forwardNaive.cpp b/src/solver/pooling/forwardNaive.cpp index 6ee893617e..43406cd508 100644 --- a/src/solver/pooling/forwardNaive.cpp +++ b/src/solver/pooling/forwardNaive.cpp @@ -76,11 +76,11 @@ bool PoolingForwardNaive::IsApplicable(const ExecutionContext&, || problem.GetPooling().GetMode() == miopenPoolingAverage // || problem.GetPooling().GetMode() == miopenPoolingAverageInclusive) // && ( // - (problem.GetXDesc().GetSize() == 5 // + (problem.GetXDesc().GetNumDims() == 5 // && problem.GetXDesc().GetLayout("NCDHW") == "NCDHW" // && problem.GetYDesc().GetLayout("NCDHW") == "NCDHW") // || // - (problem.GetXDesc().GetSize() == 4 // + (problem.GetXDesc().GetNumDims() == 4 // && problem.GetXDesc().GetLayout("NCHW") == "NCHW" // && problem.GetYDesc().GetLayout("NCHW") == "NCHW") // ); @@ -94,7 +94,7 @@ PoolingForwardNaive::GetSolution(const ExecutionContext& context, const auto bot = problem.GetXDesc(); const auto top = problem.GetYDesc(); - const bool is2d = (bot.GetSize() == 4); + const bool is2d = (bot.GetNumDims() == 4); // To compact code: const auto& pooling = problem.GetPooling(); diff --git a/src/solver/pooling/forwardNd.cpp b/src/solver/pooling/forwardNd.cpp index 183e8b3705..47c042f817 100644 --- a/src/solver/pooling/forwardNd.cpp +++ b/src/solver/pooling/forwardNd.cpp @@ -108,7 +108,7 @@ bool PoolingForwardNd::IsApplicable(const ExecutionContext& context, { return problem.GetDirection() == miopen::pooling::Direction::Forward // - && problem.GetXDesc().GetSize() == 5 // + && problem.GetXDesc().GetNumDims() == 5 // && problem.GetXDesc().GetLayout("NCDHW") == "NCDHW" // && problem.GetYDesc().GetLayout("NCDHW") == "NCDHW" // && problem.GetXDesc().GetType() == problem.GetYDesc().GetType() // diff --git a/src/solver/softmax/attn_softmax.cpp b/src/solver/softmax/attn_softmax.cpp index e5c28fbfcf..15a4d2aa72 100644 --- a/src/solver/softmax/attn_softmax.cpp +++ b/src/solver/softmax/attn_softmax.cpp @@ -137,6 +137,10 @@ ConvSolution AttnSoftmax::GetSolution(const ExecutionContext& context, nullptr, // attention related parameters nullptr, // attention related parameters nullptr, // attention related parameters + nullptr, // attention related parameters + nullptr, // attention related parameters + nullptr, // attention related parameters + nullptr, // attention related parameters seq_len, nhs); }; diff --git a/src/sqlite_db.cpp b/src/sqlite_db.cpp index f01c6c0b5c..6021a4503a 100644 --- a/src/sqlite_db.cpp +++ b/src/sqlite_db.cpp @@ -95,10 +95,11 @@ class SQLite::impl if(is_system) { - const auto& it_p = miopen_data().find(filepath.filename().string() + ".o"); + const auto& it_p = + miopen_data().find(make_object_file_name(filepath.filename()).string()); if(it_p == miopen_data().end()) { - MIOPEN_LOG_I("Unknown database: " + filepath.string() + " in internal file cache"); + MIOPEN_LOG_I("Unknown database: " << filepath << " in internal file cache"); return SQLITE_ERROR; } const auto& p = it_p->second; @@ -349,12 +350,13 @@ std::string SQLite::Statement::ColumnText(int idx) reinterpret_cast(sqlite3_column_text(pImpl->ptrStmt.get(), idx)), bytes}; } -std::string SQLite::Statement::ColumnBlob(int idx) +std::vector SQLite::Statement::ColumnBlob(int idx) { - auto ptr = sqlite3_column_blob(pImpl->ptrStmt.get(), idx); + auto ptr = static_cast(sqlite3_column_blob(pImpl->ptrStmt.get(), idx)); auto sz = sqlite3_column_bytes(pImpl->ptrStmt.get(), idx); - return std::string{reinterpret_cast(ptr), static_cast(sz)}; + return {ptr, ptr + sz}; } + int64_t SQLite::Statement::ColumnInt64(int idx) { return sqlite3_column_int64(pImpl->ptrStmt.get(), idx); @@ -366,7 +368,8 @@ int SQLite::Statement::BindText(int idx, const std::string& txt) pImpl->ptrStmt.get(), idx, txt.data(), txt.size(), SQLITE_TRANSIENT); // NOLINT return 0; } -int SQLite::Statement::BindBlob(int idx, const std::string& blob) + +int SQLite::Statement::BindBlob(int idx, const std::vector& blob) { sqlite3_bind_blob( pImpl->ptrStmt.get(), idx, blob.data(), blob.size(), SQLITE_TRANSIENT); // NOLINT @@ -379,8 +382,8 @@ int SQLite::Statement::BindInt64(int idx, const int64_t num) return 0; } -SQLitePerfDb::SQLitePerfDb(const std::string& filename_, bool is_system_) - : SQLiteBase(filename_, is_system_) +SQLitePerfDb::SQLitePerfDb(DbKinds db_kind, const std::string& filename_, bool is_system_) + : SQLiteBase(db_kind, filename_, is_system_) { if(DisableUserDbFileIO && !is_system) return; @@ -457,7 +460,7 @@ SQLitePerfDb::SQLitePerfDb(const std::string& filename_, bool is_system_) } if(!CheckTableColumns("perf_db", {"solver", "config", "params"})) { - MIOPEN_LOG_W("Invalid fields in table: perf_db disabling access to " + filename); + MIOPEN_LOG_W("Invalid fields in table: perf_db disabling access to " << filename); dbInvalid = true; } } diff --git a/src/tensor.cpp b/src/tensor.cpp index bf887e0c5d..1d2ed405af 100644 --- a/src/tensor.cpp +++ b/src/tensor.cpp @@ -272,6 +272,12 @@ TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, const int* return MakeDescriptor(t, GetDefaultLayout(), plens, size); } +TensorDescriptor +TensorDescriptor::MakeDescriptor(miopenDataType_t t, const std::size_t* plens, int size) +{ + return MakeDescriptor(t, GetDefaultLayout(), plens, size); +} + TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, miopenTensorLayout_t layout, const int* plens, @@ -283,6 +289,17 @@ TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, return {t, layout, std::vector(plens, plens + size)}; } +TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, + miopenTensorLayout_t layout, + const std::size_t* plens, + int size) +{ + if(plens == nullptr || size <= 0) + MIOPEN_THROW(miopenStatusInvalidValue); + + return {t, layout, std::vector(plens, plens + size)}; +} + TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, const int* plens, const int* pstrides, @@ -294,6 +311,19 @@ TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, return {t, std::vector(plens, plens + size), std::vector(pstrides, pstrides + size)}; } +TensorDescriptor TensorDescriptor::MakeDescriptor(miopenDataType_t t, + const std::size_t* plens, + const std::size_t* pstrides, + int size) +{ + if(plens == nullptr || pstrides == nullptr || size <= 0) + MIOPEN_THROW(miopenStatusInvalidValue); + + return {t, + std::vector(plens, plens + size), + std::vector(pstrides, pstrides + size)}; +} + void TensorDescriptor::CalculateStrides() { if(lens.empty()) @@ -331,15 +361,10 @@ const std::vector& TensorDescriptor::GetLengths() const { return le const std::vector& TensorDescriptor::GetStrides() const { return strides; } -int TensorDescriptor::GetSize() const -{ - assert(lens.size() == strides.size()); - return lens.size(); -} +unsigned TensorDescriptor::GetNumDims() const { return lens.size(); } std::size_t TensorDescriptor::GetElementSize() const { - assert(lens.size() == strides.size()); return std::accumulate(lens.begin(), lens.end(), vector_length, std::multiplies()); } @@ -378,7 +403,7 @@ std::size_t TensorDescriptor::GetIndex(std::initializer_list l) const // l is in NCHW order (MIOpen implicit logic) if(this->GetLayout_str() == "CHWNc") { - assert(l.size() - 1 <= this->GetSize()); + assert(l.size() - 1 <= this->GetNumDims()); std::initializer_list l_chwn{ *(l.begin()), *(l.begin() + 2), *(l.begin() + 3), *(l.begin() + 4), *(l.begin() + 1)}; return std::inner_product(l_chwn.begin() + 1, @@ -390,12 +415,12 @@ std::size_t TensorDescriptor::GetIndex(std::initializer_list l) const { if(!this->IsVectorized()) { - assert(l.size() <= this->GetSize()); + assert(l.size() <= this->GetNumDims()); return std::inner_product(l.begin(), l.end(), strides.begin(), std::size_t{0}); } else { - assert(l.size() - 1 <= this->GetSize()); + assert(l.size() - 1 <= this->GetNumDims()); return std::inner_product( l.begin() + 1, l.end(), strides.begin(), static_cast(*(l.begin()))); } @@ -430,7 +455,7 @@ std::size_t TensorDescriptor::GetNumBytes() const bool TensorDescriptor::IsPacked() const { return this->packed; } -bool TensorDescriptor::AllDimsFitIntoInt() const +bool TensorDescriptor::AllLengthsFitIntoInt() const { if(std::any_of(lens.cbegin(), lens.cend(), [](std::size_t x) { return x > std::numeric_limits::max(); @@ -438,6 +463,13 @@ bool TensorDescriptor::AllDimsFitIntoInt() const { return false; } + return true; +} + +bool TensorDescriptor::AllDimsFitIntoInt() const +{ + if(!AllLengthsFitIntoInt()) + return false; if(std::any_of(strides.cbegin(), strides.cend(), [](std::size_t x) { return x > std::numeric_limits::max(); })) diff --git a/src/tensor_api.cpp b/src/tensor_api.cpp index 9f742a77aa..494994b1d6 100644 --- a/src/tensor_api.cpp +++ b/src/tensor_api.cpp @@ -199,6 +199,33 @@ extern "C" miopenStatus_t miopenSetTensorDescriptor(miopenTensorDescriptor_t ten }); } +extern "C" miopenStatus_t miopenSetTensorDescriptorV2(miopenTensorDescriptor_t tensorDesc, + miopenDataType_t dataType, + int nbDims, + const size_t* dimsA, + const size_t* stridesA) +{ + if(miopen::IsLoggingFunctionCalls()) + { + const miopen::logger::CArray dim(dimsA, nbDims); + const miopen::logger::CArray stride(stridesA, nbDims); + MIOPEN_LOG_FUNCTION(tensorDesc, dataType, nbDims, dim.values, stride.values); + } + + return miopen::try_([&] { + if(stridesA == nullptr) + { + miopen::deref(tensorDesc) = + miopen::TensorDescriptor::MakeDescriptor(dataType, dimsA, nbDims); + } + else + { + miopen::deref(tensorDesc) = + miopen::TensorDescriptor::MakeDescriptor(dataType, dimsA, stridesA, nbDims); + } + }); +} + extern "C" miopenStatus_t miopenSetTensorCastType(miopenTensorDescriptor_t tensorDesc, miopenDataType_t cast_type) { @@ -248,7 +275,7 @@ extern "C" miopenStatus_t miopenGetTensorDescriptorSize(miopenTensorDescriptor_t int* size) { MIOPEN_LOG_FUNCTION(tensorDesc); - return miopen::try_([&] { miopen::deref(size) = miopen::deref(tensorDesc).GetSize(); }); + return miopen::try_([&] { miopen::deref(size) = miopen::deref(tensorDesc).GetNumDims(); }); } extern "C" miopenStatus_t miopenGetTensorDescriptor(miopenTensorDescriptor_t tensorDesc, diff --git a/src/tmp_dir.cpp b/src/tmp_dir.cpp index 3d1ae39b65..0043c46eb3 100644 --- a/src/tmp_dir.cpp +++ b/src/tmp_dir.cpp @@ -32,44 +32,63 @@ #include #include +#include +#include + MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_SAVE_TEMP_DIR) MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_DEBUG_EXIT_STATUS_TEMP_DIR) namespace miopen { -TmpDir::TmpDir(std::string prefix) - : path(fs::temp_directory_path() / - boost::filesystem::unique_path("miopen-" + prefix + "-%%%%-%%%%-%%%%-%%%%").string()) +TmpDir::TmpDir(std::string_view prefix) : path{fs::temp_directory_path()} { - fs::create_directories(this->path); -} + std::string p{prefix.empty() ? "" : (prefix[0] == '-' ? "" : "-")}; -TmpDir& TmpDir::operator=(TmpDir&& other) noexcept -{ - this->path = other.path; - other.path = ""; - return *this; + path /= boost::filesystem::unique_path("miopen" + p.append(prefix) + "-%%%%-%%%%-%%%%-%%%%") + .string(); + + fs::create_directories(path); } -void TmpDir::Execute(std::string_view exe, std::string_view args) const +int TmpDir::Execute(std::string_view cmd, std::string_view args) const { if(miopen::IsEnabled(ENV(MIOPEN_DEBUG_SAVE_TEMP_DIR))) { - MIOPEN_LOG_I2(this->path.string()); + MIOPEN_LOG_I2(path); } - auto status = Process(exe)(args, this->path); + auto status = Process{cmd}(args, path); if(miopen::IsEnabled(ENV(MIOPEN_DEBUG_EXIT_STATUS_TEMP_DIR))) { MIOPEN_LOG_I2(status); } + return status; } TmpDir::~TmpDir() { if(!miopen::IsEnabled(ENV(MIOPEN_DEBUG_SAVE_TEMP_DIR))) { +#ifdef _WIN32 + constexpr int remove_max_retries = 5; + int count = 0; + while(count < remove_max_retries) + { + try + { + fs::remove_all(path); + break; + } + catch(const fs::filesystem_error& err) + { + MIOPEN_LOG_W(err.what()); + std::this_thread::sleep_for(std::chrono::milliseconds{125}); + } + ++count; + } +#else if(!this->path.empty()) fs::remove_all(this->path); +#endif } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 491e4d37fd..ff3e244b5f 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -796,123 +796,11 @@ if(WORKAROUND_ISSUE_936 AND MIOPEN_TEST_HALF) SET(MIOPEN_TEST_FLOAT_ARG ${SAVE_MIOPEN_TEST_FLOAT_ARG}) endif() -add_custom_test(test_conv_group SKIP_UNLESS_ALL GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED -COMMAND $ --verbose --input 16 128 56 56 --weights 256 4 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 16 256 56 56 --weights 512 8 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32 -COMMAND $ --verbose --input 16 256 28 28 --weights 512 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 16 512 28 28 --weights 1024 16 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32 -COMMAND $ --verbose --input 16 512 14 14 --weights 1024 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 16 1024 14 14 --weights 2048 32 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32 -COMMAND $ --verbose --input 16 1024 7 7 --weights 2048 32 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 32 128 56 56 --weights 256 4 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 32 256 56 56 --weights 512 8 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32 -# -# Workaround for "Memory access fault by GPU node" during "HIP Release All" - WrW disabled. -COMMAND $ --verbose --input 32 256 28 28 --weights 512 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 --disable-backward-weights -COMMAND $ --verbose --input 32 512 28 28 --weights 1024 16 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32 -COMMAND $ --verbose --input 32 512 14 14 --weights 1024 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 32 1024 14 14 --weights 2048 32 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32 -COMMAND $ --verbose --input 32 1024 7 7 --weights 2048 32 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 -COMMAND $ --verbose --input 4 4 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 4 -COMMAND $ --verbose --input 8 2 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2 -COMMAND $ --verbose --input 16 4 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 4 -COMMAND $ --verbose --input 32 2 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2 -COMMAND $ --verbose --input 4 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2 -COMMAND $ --verbose --input 8 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2 -COMMAND $ --verbose --input 16 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2 -COMMAND $ --verbose --input 32 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2 -COMMAND $ --verbose --input 16 4 48 480 --weights 16 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4 -COMMAND $ --verbose --input 16 16 24 240 --weights 32 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 16 -COMMAND $ --verbose --input 16 32 12 120 --weights 64 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4 -COMMAND $ --verbose --input 16 64 6 60 --weights 128 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4 -COMMAND $ --verbose --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 3 -COMMAND $ --verbose --input 8 64 54 54 --weights 64 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 8 -COMMAND $ --verbose --input 8 128 27 27 --weights 128 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 8 -COMMAND $ --verbose --input 8 3 224 224 --weights 63 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 3 -COMMAND $ --verbose --input 8 64 112 112 --weights 128 32 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 2 -COMMAND $ --verbose --input 16 9 224 224 --weights 63 3 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 3 -# -# Workaround for "Memory access fault by GPU node" during "FP32 gfx908 Hip Release All subset" - WrW disabled. -COMMAND $ --verbose --input 16 64 112 112 --weights 128 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4 --disable-backward-weights -COMMAND $ --verbose --input 16 3 224 224 --weights 63 1 7 7 --pads_strides_dilations 3 3 2 2 1 1 --group-count 3 -COMMAND $ --verbose --input 16 192 28 28 --weights 32 12 5 5 --pads_strides_dilations 2 2 1 1 1 1 --group-count 16 -COMMAND $ --verbose --input 16 832 7 7 --weights 128 52 5 5 --pads_strides_dilations 2 2 1 1 1 1 --group-count 16 -COMMAND $ --verbose --input 16 192 28 28 --weights 32 24 1 1 --pads_strides_dilations 0 0 1 1 1 1 --group-count 8 -COMMAND $ --verbose --input 16 832 7 7 --weights 128 104 1 1 --pads_strides_dilations 0 0 1 1 1 1 --group-count 8 -COMMAND $ --verbose --input 11 23 161 700 --weights 46 1 7 7 --pads_strides_dilations 1 1 2 2 1 1 --group-count 23 -COMMAND $ --verbose --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 7 -COMMAND $ --verbose --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 0 0 1 1 1 1 --group-count 7 -COMMAND $ --verbose --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 0 0 2 2 1 1 --group-count 7 -COMMAND $ --verbose --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 7 -COMMAND $ --verbose --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 2 2 2 2 1 1 --group-count 7 -COMMAND $ --verbose --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 3 -COMMAND $ --verbose --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 0 0 1 1 1 1 --group-count 3 -COMMAND $ --verbose --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 0 0 2 2 1 1 --group-count 3 -COMMAND $ --verbose --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 3 -COMMAND $ --verbose --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 2 2 2 2 1 1 --group-count 3 -) - - - - if(MIOPEN_TEST_DEEPBENCH) add_custom_test(test_deepbench_rnn GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED ) endif() -add_custom_test(test_conv_extra SKIP_UNLESS_ALL GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED -# COMMAND $ --verbose --input 1 1 1 1 --weights 1 1 2 2 --pads_strides_dilations 0 0 3 3 1 1 -COMMAND $ --verbose --input 4 1 161 700 --weights 4 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 -COMMAND $ --verbose --input 4 1 161 700 --weights 4 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 -COMMAND $ --verbose --input 4 32 79 341 --weights 4 32 5 10 --pads_strides_dilations 0 0 2 2 1 1 -COMMAND $ --verbose --input 4 32 79 341 --weights 4 32 5 10 --pads_strides_dilations 0 0 2 2 1 1 -COMMAND $ --verbose --input 4 3 227 227 --weights 4 3 11 11 --pads_strides_dilations 0 0 4 4 1 1 -COMMAND $ --verbose --input 4 3 224 224 --weights 4 3 11 11 --pads_strides_dilations 2 2 4 4 1 1 -COMMAND $ --verbose --input 16 1 48 480 --weights 16 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 -# Forward disabled since FFT fails verification for the forward direction -COMMAND $ --verbose --input 32 64 27 27 --weights 192 64 5 5 --pads_strides_dilations 2 2 1 1 1 1 --disable-forward -# COMMAND $ --verbose --input 4 64 14 14 --weights 24 64 5 5 --pads_strides_dilations 2 2 1 1 1 1 -COMMAND $ --verbose --input 4 96 14 14 --weights 32 96 5 5 --pads_strides_dilations 2 2 1 1 1 1 -COMMAND $ --verbose --input 4 16 14 14 --weights 4 16 5 5 --pads_strides_dilations 2 2 1 1 1 1 -COMMAND $ --verbose --input 4 32 14 14 --weights 4 32 5 5 --pads_strides_dilations 2 2 1 1 1 1 - -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 16 3 64 128 --weights 96 3 11 11 --pads_strides_dilations 0 0 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 16 3 32 32 --weights 96 3 11 11 --pads_strides_dilations 0 0 2 2 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 16 3 64 128 --weights 96 3 11 11 --pads_strides_dilations 5 5 2 2 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 16 3 32 32 --weights 96 3 11 11 --pads_strides_dilations 5 5 2 2 1 1 ${MIOPEN_TEST_FLAGS_ARGS} - -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 2 16 1024 2048 --weights 32 16 3 3 --pads_strides_dilations 0 0 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 2 16 1024 2048 --weights 32 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 2 16 3072 3072 --weights 32 16 3 3 --pads_strides_dilations 0 0 2 2 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 2 16 3072 3072 --weights 32 16 3 3 --pads_strides_dilations 2 2 2 2 1 1 ${MIOPEN_TEST_FLAGS_ARGS} - -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 128 320 1 7 --weights 256 320 1 1 --pads_strides_dilations 0 0 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 128 1024 1 7 --weights 2048 1024 1 1 --pads_strides_dilations 1 1 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 352 192 7 1 --weights 320 192 1 1 --pads_strides_dilations 0 0 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --input 352 16 7 1 --weights 32 16 1 1 --pads_strides_dilations 2 2 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} -) - - -add_custom_test(test_conv_3d SKIP_UNLESS_ALL GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED -COMMAND $ --verbose --conv_dim_type conv3d --input 16 32 4 9 9 --weights 64 32 3 3 3 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --group-count 1 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 4 3 4 227 227 --weights 4 3 3 11 11 --pads_strides_dilations 0 0 0 1 1 1 1 1 1 --group-count 1 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 16 128 4 56 56 --weights 256 4 3 3 3 --pads_strides_dilations 1 1 1 1 1 1 1 1 1 --group-count 32 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 16 128 56 56 56 --weights 256 4 3 3 3 --pads_strides_dilations 1 2 3 1 1 1 1 2 3 --group-count 32 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 4 4 4 161 700 --weights 32 1 3 5 20 --pads_strides_dilations 1 1 1 2 2 2 1 1 1 --group-count 4 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 8 512 4 28 28 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 1 1 1 1 1 --group-count 4 --cmode conv --pmode same -COMMAND $ --verbose --conv_dim_type conv3d --input 8 512 4 56 56 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --group-count 4 --cmode conv --pmode same -COMMAND $ --verbose --conv_dim_type conv3d --input 8 512 3 14 14 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --trans_output_pads 0 0 0 --group-count 1 --cmode trans --pmode same -COMMAND $ --verbose --conv_dim_type conv3d --input 16 64 3 4 4 --weights 64 32 1 3 3 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --trans_output_pads 0 0 0 --group-count 4 --cmode trans --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 16 32 4 9 9 --weights 64 32 3 3 3 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --group-count 1 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 4 3 4 227 227 --weights 4 3 3 11 11 --pads_strides_dilations 0 0 0 1 1 1 1 2 3 --group-count 1 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 16 128 4 56 56 --weights 256 4 3 3 3 --pads_strides_dilations 1 2 3 1 1 1 1 2 3 --group-count 32 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 4 4 4 161 700 --weights 32 1 3 5 20 --pads_strides_dilations 1 2 3 1 2 3 1 2 3 --group-count 4 --cmode conv --pmode default -COMMAND $ --verbose --conv_dim_type conv3d --input 8 512 4 28 28 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 1 1 1 2 3 --group-count 4 --cmode conv --pmode same -COMMAND $ --verbose --conv_dim_type conv3d --input 8 512 4 56 56 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --group-count 4 --cmode conv --pmode same -COMMAND $ --verbose --conv_dim_type conv3d --input 8 512 3 14 14 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --trans_output_pads 0 0 0 --group-count 1 --cmode trans --pmode same -COMMAND $ --verbose --conv_dim_type conv3d --input 16 64 3 4 4 --weights 64 32 1 3 3 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --trans_output_pads 0 0 0 --group-count 4 --cmode trans --pmode default -) - set(DYNAMIC_IMPLICITGEMM_COMMON MIOPEN_FIND_MODE=normal) set(DYNAMIC_IMPLICITGEMM_ENVS @@ -1009,34 +897,6 @@ set(CONV_CK_IGEMM_FWD_V6R1_DLOPS_NCHW_ENV MIOPEN_DEBUG_FIND_ONLY_SOLVER=ConvCkIgemmFwdV6r1DlopsNchw MIOPEN_DEBUG_CONV_CK_IGEMM_FWD_V6R1_DLOPS_NCHW=1) -add_custom_test(test_reduce_custom_fp32 GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED SKIP_UNLESS_ALL -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 1024 30528 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -) - -add_custom_test(test_reduce_custom_fp32_fp16 HALF_ENABLED GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED SKIP_UNLESS_ALL -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 8 2 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 160 10 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 7 1024 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 1 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 1 1 --I 0 --N 1 ---ReduceOp 1 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 1 1 --I 1 --N 1 ---ReduceOp 3 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 2 1 --I 1 --N 1 ---ReduceOp 3 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 6 2 1 --I 0 --N 1 ---ReduceOp 3 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 6 2 1 --I 0 --N 1 ---ReduceOp 2 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 2 2 1 --I 0 --N 1 ---ReduceOp 0 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 4 3 1 --I 0 --N 1 ---ReduceOp 3 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 3 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 3 --R 0 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 2048 32 1 --I 0 --N 1 ---ReduceOp 3 --R 0 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 4 3 1 --I 0 --N 1 ---ReduceOp 2 --R 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 2 --R 0 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 2048 32 1 --I 0 --N 1 ---ReduceOp 2 --R 0 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 2 --R 0 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 12 11 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 13 4 7 7 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 3 ${MIOPEN_TEST_FLAGS_ARGS} -COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --scales 1 0 --CompType 1 --D 64 3 280 81 --I 0 --N 0 --ReduceOp 0 --R 0 ${MIOPEN_TEST_FLAGS_ARGS} -) - if(MIOPEN_TEST_FLOAT) add_custom_test(test_reduce_double SKIP_UNLESS_ALL GFX94X_ENABLED GFX103X_ENABLED GFX110X_ENABLED COMMAND $ --double --all --verbose) endif() @@ -1132,52 +992,12 @@ add_custom_test(smoke_solver_ConvMlirIgemm_W GFX900_DISABLED GFX908_DISABLED GFX ENVIRONMENT MIOPEN_FIND_ENFORCE=SEARCH_DB_UPDATE MIOPEN_DEBUG_TUNING_ITERATIONS_MAX=5 ${IMPLICITGEMM_MLIR_ENV_W} COMMAND $ ${TEST_CONV_VERBOSE_W} --input 64 64 28 28 --weights 64 64 1 1 --pads_strides_dilations 0 0 1 1 1 1 ${MIOPEN_TEST_FLAGS_ARGS} ) - -add_custom_test(test_regression_half_vega SKIP_UNLESS_ALL FLOAT_DISABLED HALF_ENABLED GFX908_DISABLED GFX90A_DISABLED -# Issue #1956. - ENVIRONMENT MIOPEN_FIND_MODE=normal MIOPEN_DEBUG_FIND_ONLY_SOLVER='GemmBwdRest' - COMMAND $ ${TEST_CONV_VERBOSE_B} --cmode conv --pmode default --group-count 1 --batch_size 2 --input_channels 64 --output_channels 32 --spatial_dim_elements 128 128 128 --filter_dims 3 3 3 --pads_strides_dilations 1 1 1 1 1 1 1 1 1 --trans_output_pads 0 0 0 --in_layout NCDHW --fil_layout NCDHW --out_layout NCDHW ${MIOPEN_TEST_FLAGS_ARGS} -) -# Test case for issue #1956 uses huge tensors, therefore: -set_tests_properties(test_regression_half_vega PROPERTIES RUN_SERIAL On) - -set(ENVS_REGRESSION_ISSUE_1012 - MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS=1 - MIOPEN_FIND_MODE=normal) - -set(ARGS_REGRESSION_ISSUE_1012 - --verbose - --disable-forward - --disable-backward-data - --disable-validation) - -add_custom_test(test_regression_issue_2012 GFX900_DISABLED GFX906_DISABLED GFX90A_DISABLED - # Issue #1012. - ENVIRONMENT ${ENVS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 128, 832, 7, 7 --weights 32, 832, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 64, 192, 28, 28 --weights 64, 192, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 64, 256, 28, 28 --weights 128, 256, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 64, 480, 14, 14 --weights 64, 480, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 64, 512, 14, 14 --weights 128, 512, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 64, 512, 28, 28 --weights 128, 512, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --group-count 1 --input 64, 64, 56, 56 --weights 256, 64, 1, 1 --pads_strides_dilations 0 0 1 1 1 1 ${ARGS_REGRESSION_ISSUE_1012} -) - -set(ENVS_FIND_ONLY_HIP_IGEMM_V4R4XDLOPS - MIOPEN_FIND_MODE=normal - MIOPEN_DEBUG_IMPLICIT_GEMM_FIND_ALL_SOLUTIONS=1 - MIOPEN_DEBUG_FIND_ONLY_SOLVER=ConvHipImplicitGemmForwardV4R4Xdlops) - -set(ARGS_ENABLE_FORWARD_ONLY - --verbose - --disable-backward-data - --disable-backward-weights) - -add_custom_test(test_regression_half_mi200 GFX900_DISABLED GFX906_DISABLED GFX908_DISABLED FLOAT_DISABLED HALF_ENABLED - # Issue-internal #4 - ENVIRONMENT ${ENVS_FIND_ONLY_HIP_IGEMM_V4R4XDLOPS} - COMMAND $ ${MIOPEN_TEST_FLOAT_ARG} --cmode conv --pmode default --input 120 64 75 75 --weights 128 64 1 1 --pads_strides_dilations 0 0 2 2 1 1 ${ARGS_ENABLE_FORWARD_ONLY} -) -#override if we need to install gtests +#Dont install the tests if we are building each test separately +#When the tests are built as a single unit, then we want to be +#able to package them set(INSTALL_GTEST OFF) +if(MIOPEN_TEST_DISCRETE) add_subdirectory(gtest EXCLUDE_FROM_ALL) +else() +add_subdirectory(gtest) +endif() diff --git a/test/conv3d.hpp b/test/conv3d.hpp index 2b49c0f10e..33542587f4 100644 --- a/test/conv3d.hpp +++ b/test/conv3d.hpp @@ -56,5 +56,11 @@ struct conv3d_driver : conv_driver this->add(this->in_layout, "in_layout", this->generate_data({"NCDHW"})); this->add(this->fil_layout, "fil_layout", this->generate_data({"NCDHW"})); this->add(this->out_layout, "out_layout", this->generate_data({"NCDHW"})); + this->add(this->deterministic, "deterministic", this->generate_data({false})); + this->add(this->tensor_vect, "tensor_vect", this->generate_data({0})); + this->add(this->vector_length, "vector_length", this->generate_data({1})); + // Only valid for int8 input and weights + this->add(this->output_type, "output_type", this->generate_data({"int32"})); + this->add(this->int8_vectorize, "int8_vectorize", this->generate_data({false})); } }; diff --git a/test/conv3d_find2.cpp b/test/conv3d_find2.cpp index 0a36c8958a..198b6890cc 100644 --- a/test/conv3d_find2.cpp +++ b/test/conv3d_find2.cpp @@ -56,6 +56,12 @@ struct conv3d_find2_driver : conv_driver this->add(this->in_layout, "in_layout", this->generate_data({"NCDHW"})); this->add(this->fil_layout, "fil_layout", this->generate_data({"NCDHW"})); this->add(this->out_layout, "out_layout", this->generate_data({"NCDHW"})); + this->add(this->deterministic, "deterministic", this->generate_data({false})); + this->add(this->tensor_vect, "tensor_vect", this->generate_data({0})); + this->add(this->vector_length, "vector_length", this->generate_data({1})); + // Only valid for int8 input and weights + this->add(this->output_type, "output_type", this->generate_data({"int32"})); + this->add(this->int8_vectorize, "int8_vectorize", this->generate_data({false})); } }; diff --git a/test/conv_common.hpp b/test/conv_common.hpp index 0f5c63051a..9bcba030fc 100644 --- a/test/conv_common.hpp +++ b/test/conv_common.hpp @@ -66,6 +66,8 @@ using ExecutionContext = miopen::ExecutionContext; using ConvProblemDescription = miopen::conv::ProblemDescription; using Direction = miopen::conv::Direction; +bool get_handle_xnack(); + #if TEST_DIRECT_SUPPORTED_CONFIG_ONLY static inline bool is_direct_fwd_bwd_data_supported(miopen::Handle& handle, const miopen::ConvolutionDescriptor convDesc, @@ -187,7 +189,7 @@ tensor get_output_tensor(const miopen::ConvolutionDescriptor& filter, std::string yLayout = out_layout.empty() - ? input.desc.GetLayout(miopen::tensor_layout_get_default(input.desc.GetSize())) + ? input.desc.GetLayout(miopen::tensor_layout_get_default(input.desc.GetNumDims())) : out_layout; return tensor{filter.GetForwardOutputTensorWithLayout( input.desc, weights.desc, yLayout, miopen_type{})}; @@ -2060,12 +2062,13 @@ struct conv_driver : test_driver .generate(tensor_elem_gen_integer{17}); } - if(input.desc.GetSize() != in_layout.size() || - weights.desc.GetSize() != fil_layout.size() || input.desc.GetSize() != out_layout.size()) + if(input.desc.GetNumDims() != in_layout.size() || + weights.desc.GetNumDims() != fil_layout.size() || + input.desc.GetNumDims() != out_layout.size()) { - std::cout << input.desc.GetSize() << "," << in_layout.size() << std::endl; - std::cout << weights.desc.GetSize() << "," << fil_layout.size() << std::endl; - std::cout << input.desc.GetSize() << "," << out_layout.size() << std::endl; + std::cout << input.desc.GetNumDims() << "," << in_layout.size() << std::endl; + std::cout << weights.desc.GetNumDims() << "," << fil_layout.size() << std::endl; + std::cout << input.desc.GetNumDims() << "," << out_layout.size() << std::endl; std::cerr << "FAILED: layout not match dimension size!" << std::endl; return; } @@ -2080,7 +2083,7 @@ struct conv_driver : test_driver std::vector dim_strides; miopen::tensor_layout_to_strides( dim_lens, - miopen::tensor_layout_get_default(weights.desc.GetSize()), + miopen::tensor_layout_get_default(weights.desc.GetNumDims()), in_layout, vector_length, dim_strides); @@ -2092,14 +2095,15 @@ struct conv_driver : test_driver std::vector dim_strides; miopen::tensor_layout_to_strides( dim_lens, - miopen::tensor_layout_get_default(weights.desc.GetSize()), + miopen::tensor_layout_get_default(weights.desc.GetNumDims()), fil_layout, vector_length, dim_strides); weights.desc = miopen::TensorDescriptor(miopen_type{}, dim_lens, dim_strides); } - if(input.desc.GetSize() != 2 + spatial_dim || weights.desc.GetSize() != 2 + spatial_dim || + if(input.desc.GetNumDims() != 2 + spatial_dim || + weights.desc.GetNumDims() != 2 + spatial_dim || pads_strides_dilations.size() != 3 * spatial_dim || trans_output_pads.size() != spatial_dim) { @@ -2148,9 +2152,12 @@ struct conv_driver : test_driver bool is_bfloat16 = (input.desc.GetType() == miopenBFloat16 && weights.desc.GetType() == miopenBFloat16); - // bfloat16 is not supported for conv3d if(is_bfloat16 && !(filter.spatialDim == 2)) + { + show_command(); + std::cout << "Skipped: bfloat16 is supported for 2D conv only" << std::endl; return; + } if(((filter.mode == miopenTranspose) && ((filter.group_count == 1 && in_c_len == wei_k_len) || @@ -2167,6 +2174,8 @@ struct conv_driver : test_driver { if(miopen::any_of(filter.GetConvStrides(), [](auto v) { return v == 0; })) { + show_command(); + std::cout << "Skipped: stride[i] == 0" << std::endl; return; } @@ -2194,13 +2203,19 @@ struct conv_driver : test_driver if(miopen::any_of(out_spatial_len, [](auto v) { return v <= 0; })) { + show_command(); + std::cout << "Skipped: out_spatial_len[i] <= 0" << std::endl; return; } } else if(filter.paddingMode == miopenPaddingValid) { if(miopen::any_of(filter.GetConvStrides(), [](auto v) { return v == 0; })) + { + show_command(); + std::cout << "Skipped: stride[i] == 0" << std::endl; return; + } std::vector out_spatial_len(spatial_dim); @@ -2216,6 +2231,8 @@ struct conv_driver : test_driver if(miopen::any_of(out_spatial_len, [](auto v) { return v <= 0; })) { + show_command(); + std::cout << "Skipped: out_spatial_len[i] <= 0" << std::endl; return; } } @@ -2289,16 +2306,6 @@ struct conv_driver : test_driver } #endif - // bwd53 kernel (large images supported) doesnt support stride !=1 and dilation and - // pad. - if(filter.GetSpatialDimension() == 2 && in_spatial_len[1] >= 2048 && - ((filter.GetConvStrides()[0] != 1) || (filter.GetConvStrides()[1] != 1) || - (filter.GetConvDilations()[0] != 1) || (filter.GetConvDilations()[1] != 1) || - (filter.GetConvPads()[1] != 0) || (filter.GetConvPads()[0] != 0))) - { - return; - } - input.generate(gen_positive_value); output.generate(gen_positive_value); weights.generate(gen_sign_value); @@ -2425,6 +2432,12 @@ struct conv_driver : test_driver search, int8_vectorize}); } + else + { + show_command(); + std::cout << "FAILED: bad output_type: '" << output_type << '\'' + << std::endl; + } } else { @@ -2499,7 +2512,7 @@ struct conv_bias_driver : test_driver { for(int i = 2; i < 4; i++) { - if(output.desc.GetSize() == i + 2) + if(output.desc.GetNumDims() == i + 2) return i; } return -1; diff --git a/test/cpu_argmax.hpp b/test/cpu_argmax.hpp index c70487019e..08f40c9c01 100644 --- a/test/cpu_argmax.hpp +++ b/test/cpu_argmax.hpp @@ -36,7 +36,7 @@ void cpu_argmax_forward(tensor input, tensor& ref_output, int32_t dim) auto reduce_size = input_dims[dim]; auto output_numel = - std::accumulate(output_dims.begin(), output_dims.end(), 1L, std::multiplies()); + std::accumulate(output_dims.begin(), output_dims.end(), 1LL, std::multiplies()); auto inner_size = 1ULL; for(int32_t i = dim + 1; i < input_dims.size(); i++) diff --git a/test/cpu_bias.hpp b/test/cpu_bias.hpp index 17f04377a7..865b19c02a 100644 --- a/test/cpu_bias.hpp +++ b/test/cpu_bias.hpp @@ -43,7 +43,7 @@ template void cpu_bias_forward_impl(tensor& out, const tensor& bias) { - assert(out.desc.GetSize() == NSpatialDim + 2 and bias.desc.GetSize() == NSpatialDim + 2); + assert(out.desc.GetNumDims() == NSpatialDim + 2 and bias.desc.GetNumDims() == NSpatialDim + 2); assert( bias.desc.GetLengths()[0] == 1 && bias.desc.GetLengths()[1] == out.desc.GetLengths()[1] && std::all_of(bias.desc.GetLengths().begin() + 2, bias.desc.GetLengths().end(), [](auto v) { @@ -59,7 +59,7 @@ void cpu_bias_forward_impl(tensor& out, const tensor& bias) template void cpu_bias_backward_data_impl(const tensor& out, tensor& bias) { - assert(out.desc.GetSize() == NSpatialDim + 2 and bias.desc.GetSize() == NSpatialDim + 2); + assert(out.desc.GetNumDims() == NSpatialDim + 2 and bias.desc.GetNumDims() == NSpatialDim + 2); assert( bias.desc.GetLengths()[0] == 1 && bias.desc.GetLengths()[1] == out.desc.GetLengths()[0] && std::all_of(bias.desc.GetLengths().begin() + 2, bias.desc.GetLengths().end(), [](auto v) { @@ -88,7 +88,7 @@ void cpu_bias_backward_data_impl(const tensor& out, tensor& bias) template void cpu_bias_forward(tensor& out, const tensor& bias) { - switch(out.desc.GetSize()) + switch(out.desc.GetNumDims()) { case 3: { cpu_bias_forward_impl<1>(out, bias); @@ -115,7 +115,7 @@ void cpu_bias_forward(tensor& out, const tensor& bias) template void cpu_bias_backward_data(const tensor& out, tensor& bias) { - switch(out.desc.GetSize()) + switch(out.desc.GetNumDims()) { case 3: { cpu_bias_backward_data_impl<1>(out, bias); diff --git a/test/cpu_conv.hpp b/test/cpu_conv.hpp index cf2427b0d4..3436f832de 100644 --- a/test/cpu_conv.hpp +++ b/test/cpu_conv.hpp @@ -90,8 +90,8 @@ void cpu_convolution_forward_impl(const tensor& in, FW fw = {}) { static_assert(ConvDim > 0, "wrong! convolution dim should be larger than 0"); - assert(in.desc.GetSize() == ConvDim + 2 and wei.desc.GetSize() == ConvDim + 2 and - out.desc.GetSize() == ConvDim + 2 and pads.size() == ConvDim and + assert(in.desc.GetNumDims() == ConvDim + 2 and wei.desc.GetNumDims() == ConvDim + 2 and + out.desc.GetNumDims() == ConvDim + 2 and pads.size() == ConvDim and strides.size() == ConvDim and dilations.size() == ConvDim); std::size_t out_n_len = out.desc.GetLengths()[0]; @@ -212,8 +212,8 @@ void cpu_convolution_backward_data_impl(tensor& in, FO fo = {}) { static_assert(ConvDim > 0, "wrong! convolution dim should be larger than 0"); - assert(in.desc.GetSize() == ConvDim + 2 and wei.desc.GetSize() == ConvDim + 2 and - out.desc.GetSize() == ConvDim + 2 and pads.size() == ConvDim and + assert(in.desc.GetNumDims() == ConvDim + 2 and wei.desc.GetNumDims() == ConvDim + 2 and + out.desc.GetNumDims() == ConvDim + 2 and pads.size() == ConvDim and strides.size() == ConvDim and dilations.size() == ConvDim); std::size_t in_n_len = in.desc.GetLengths()[0]; @@ -306,8 +306,8 @@ void cpu_convolution_backward_weight_impl(const tensor& in, FO fo) { static_assert(ConvDim > 0, "wrong! convolution dim should be larger than 0"); - assert(in.desc.GetSize() == ConvDim + 2 and wei.desc.GetSize() == ConvDim + 2 and - out.desc.GetSize() == ConvDim + 2 and pads.size() == ConvDim and + assert(in.desc.GetNumDims() == ConvDim + 2 and wei.desc.GetNumDims() == ConvDim + 2 and + out.desc.GetNumDims() == ConvDim + 2 and pads.size() == ConvDim and strides.size() == ConvDim and dilations.size() == ConvDim); std::size_t out_n_len = out.desc.GetLengths()[0]; diff --git a/test/cpu_reduce_util.hpp b/test/cpu_reduce_util.hpp index 0f520b7b3e..01accf6650 100644 --- a/test/cpu_reduce_util.hpp +++ b/test/cpu_reduce_util.hpp @@ -26,11 +26,7 @@ #ifndef GUARD_CPU_REDUCE_UTIL_HPP #define GUARD_CPU_REDUCE_UTIL_HPP -#if !defined(_WIN32) #include -#else -#include -#endif #include #include #include diff --git a/test/cpu_sum.hpp b/test/cpu_sum.hpp index 8898a5654b..73d68eb9b8 100644 --- a/test/cpu_sum.hpp +++ b/test/cpu_sum.hpp @@ -39,7 +39,7 @@ void cpu_sum_forward(tensor input, auto reduce_size = input_dims[dim]; auto output_numel = - std::accumulate(output_dims.begin(), output_dims.end(), 1L, std::multiplies()); + std::accumulate(output_dims.begin(), output_dims.end(), 1LL, std::multiplies()); auto inner_size = 1ULL; for(int32_t i = dim + 1; i < input_dims.size(); i++) diff --git a/test/ctc.cpp b/test/ctc.cpp index 1c759220f2..4dfd452d50 100644 --- a/test/ctc.cpp +++ b/test/ctc.cpp @@ -776,7 +776,7 @@ struct ctc_driver : test_driver losses = tensor{lossesDims}; std::fill(losses.begin(), losses.end(), T(0)); - size_t labels_sz = std::accumulate(labelLengths.begin(), labelLengths.end(), 0); + size_t labels_sz = std::accumulate(labelLengths.begin(), labelLengths.end(), 0ULL); auto labels = std::vector(labels_sz); int blank_lb = ctcLossDesc.blank_label_id; diff --git a/test/driver.hpp b/test/driver.hpp index dd13cc9850..f7d15b6c82 100644 --- a/test/driver.hpp +++ b/test/driver.hpp @@ -37,11 +37,7 @@ #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #include #include #include diff --git a/test/embed_sqlite.cpp b/test/embed_sqlite.cpp index 7c50f6abb1..96049ef3ee 100644 --- a/test/embed_sqlite.cpp +++ b/test/embed_sqlite.cpp @@ -73,7 +73,8 @@ struct EmbedSQLite : test_driver // Get filename for the sys db // Check it in miopen_data() fs::path pdb_path(ctx.GetPerfDbPath()); - const auto& it_p = miopen_data().find(pdb_path.filename().string() + ".o"); + const auto& it_p = + miopen_data().find(make_object_file_name(pdb_path.filename()).string()); EXPECT(it_p != miopen_data().end()); // find all the entries in perf db // Assert result is non-empty diff --git a/test/gpu_reference_kernel.cpp b/test/gpu_reference_kernel.cpp index 7faaeb788c..302a5e7a4a 100644 --- a/test/gpu_reference_kernel.cpp +++ b/test/gpu_reference_kernel.cpp @@ -36,11 +36,7 @@ #include #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #include "test.hpp" #include "driver.hpp" #include "tensor_holder.hpp" diff --git a/test/gtest/CMakeLists.txt b/test/gtest/CMakeLists.txt index 0092e16b4a..e944f29868 100644 --- a/test/gtest/CMakeLists.txt +++ b/test/gtest/CMakeLists.txt @@ -4,6 +4,7 @@ find_package(rocblas) set(SOURCES log.cpp platform.cpp + conv_common.cpp ) if(MIOPEN_BACKEND_OPENCL) @@ -27,9 +28,9 @@ function(add_gtest TEST_NAME TEST_CPP) if(NOT MIOPEN_EMBED_DB STREQUAL "") target_link_libraries(${TEST_NAME} $) endif() - if(NOT WIN32) # TODO: cannot run on Windows due to missing DLL dependencies + if(NOT WIN32 AND MIOPEN_TEST_DISCRETE) # TODO: cannot run on Windows due to missing DLL dependencies # Enable CMake to discover the test binary - gtest_discover_tests(${TEST_NAME} DISCOVERY_TIMEOUT 300 PROPERTIES ENVIRONMENT "MIOPEN_USER_DB_PATH=${CMAKE_CURRENT_BINARY_DIR};MIOPEN_TEST_FLOAT_ARG=${MIOPEN_TEST_FLOAT_ARG};MIOPEN_TEST_ALL=${MIOPEN_TEST_ALL};MIOPEN_TEST_MLIR=${MIOPEN_TEST_MLIR};MIOPEN_TEST_COMPOSABLEKERNEL=${MIOPEN_TEST_COMPOSABLEKERNEL};CODECOV_TEST=${CODECOV_TEST};MIOPEN_TEST_DBSYNC=${MIOPEN_TEST_DBSYNC};MIOPEN_TEST_CONV=${MIOPEN_TEST_CONV};MIOPEN_TEST_DEEPBENCH=${MIOPEN_TEST_DEEPBENCH};MIOPEN_DEBUG_TUNING_ITERATIONS_MAX=${MIOPEN_DEBUG_TUNING_ITERATIONS_MAX}") + gtest_discover_tests(${TEST_NAME} DISCOVERY_TIMEOUT 300 DISCOVERY_MODE PRE_TEST PROPERTIES ENVIRONMENT "MIOPEN_USER_DB_PATH=${CMAKE_CURRENT_BINARY_DIR};MIOPEN_TEST_FLOAT_ARG=${MIOPEN_TEST_FLOAT_ARG};MIOPEN_TEST_ALL=${MIOPEN_TEST_ALL};MIOPEN_TEST_MLIR=${MIOPEN_TEST_MLIR};MIOPEN_TEST_COMPOSABLEKERNEL=${MIOPEN_TEST_COMPOSABLEKERNEL};CODECOV_TEST=${CODECOV_TEST};MIOPEN_TEST_DBSYNC=${MIOPEN_TEST_DBSYNC};MIOPEN_TEST_CONV=${MIOPEN_TEST_CONV};MIOPEN_TEST_DEEPBENCH=${MIOPEN_TEST_DEEPBENCH};MIOPEN_DEBUG_TUNING_ITERATIONS_MAX=${MIOPEN_DEBUG_TUNING_ITERATIONS_MAX}") endif() target_link_libraries(${TEST_NAME} BZip2::BZip2) if(WIN32) @@ -68,7 +69,8 @@ else() if( NOT ENABLE_ASAN_PACKAGING ) install(TARGETS miopen_gtest PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE - DESTINATION ${CMAKE_INSTALL_BINDIR}) + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT client) endif() endif() diff --git a/test/gtest/ai_heuristics.hpp b/test/gtest/ai_heuristics.hpp index 3898ad9bf6..b15c5387c1 100644 --- a/test/gtest/ai_heuristics.hpp +++ b/test/gtest/ai_heuristics.hpp @@ -28,10 +28,11 @@ #include #include #include +#include struct AIModelTestCase { - struct ConvTestCaseBase conv; + struct group_conv::GroupConvTestConfig<2u> conv; miopen::conv::Direction direction; miopenDataType_t data_type; miopenTensorLayout_t layout; diff --git a/test/gtest/attention_golden.hpp b/test/gtest/attention_golden.hpp new file mode 100644 index 0000000000..bb7105016b --- /dev/null +++ b/test/gtest/attention_golden.hpp @@ -0,0 +1,453 @@ +#include + +// json_attention_golden_data calculated with pytorch +static const std::string_view json_attention_fwd_golden_data = + R"json({ + "tensor": [ + [ 3.770536184310913, + 4.329129219055176, + 4.109292030334473, + 3.92095947265625 + ], + [ 3.7122879028320312, + 4.259908199310303, + 4.0466790199279785, + 3.8648295402526855 + ], + [ 3.721836805343628, + 4.262655258178711, + 4.054609775543213, + 3.8784942626953125 + ], + [ 3.7891483306884766, + 4.350048065185547, + 4.128964424133301, + 3.9394826889038086 + ], + [ 3.7144012451171875, + 4.253789901733398, + 4.046574115753174, + 3.871335506439209 + ], + + [ 3.0347471237182617, + 3.4869349002838135, + 3.316852569580078, + 3.171729326248169 + ], + [ 2.9643800258636475, + 3.380119800567627, + 3.234912872314453, + 3.1168062686920166 + ], + [ 3.193814277648926, + 3.6726696491241455, + 3.4919581413269043, + 3.336332321166992 + ], + [ 3.173119068145752, + 3.636214017868042, + 3.4669370651245117, + 3.3240883350372314 + ], + [ 3.2662007808685303, + 3.7338991165161133, + 3.567594051361084, + 3.4292633533477783 + ] + ] +})json"; + +static const std::string_view json_attention_word_position = + R"json({ + "tensor": [ + [ 0.8401877284049988, + 0.3943829238414764, + 0.7830992341041565, + 0.7984400391578674 + ], + [ 0.9116473793983459, + 0.1975513696670532, + 0.3352227509021759, + 0.7682296037673950 + ], + [ 0.2777747213840485, + 0.5539699792861938, + 0.4773970544338226, + 0.6288709044456482 + ], + [ 0.3647844791412354, + 0.5134009122848511, + 0.9522297382354736, + 0.9161950945854187 + ], + [ 0.6357117295265198, + 0.7172969579696655, + 0.1416025608778000, + 0.6069688796997070 + ], + + [ 0.0163005720824003, + 0.2428867667913437, + 0.1372315734624863, + 0.8041767477989197 + ], + [ 0.1566790938377380, + 0.4009443819522858, + 0.1297904402017593, + 0.1088088005781174 + ], + [ 0.9989244937896729, + 0.2182569056749344, + 0.5129324197769165, + 0.8391122221946716 + ], + [ 0.6126398444175720, + 0.2960316240787506, + 0.6375522613525391, + 0.5242871642112732 + ], + [ 0.4935829937458038, + 0.9727750420570374, + 0.2925167977809906, + 0.7713577151298523 + ] + ] +})json"; + +static const std::string_view json_attention_q_weights = + R"json({ + "tensor": [ + [ 0.5267449617385864, + 0.7699138522148132, + 0.4002286195755005, + 0.8915294408798218 + ], + [ 0.2833147346973419, + 0.3524583578109741, + 0.8077245354652405, + 0.9190264940261841 + ], + + [ 0.0697552785277367, + 0.9493270516395569, + 0.5259953737258911, + 0.0860558450222015 + ], + [ 0.1922138482332230, + 0.6632269024848938, + 0.8902326226234436, + 0.3488929271697998 + ] + ] +})json"; + +static const std::string_view json_attention_k_weights = + R"json({ + "tensor": [ + [ 0.0641713216900826, + 0.0200230497866869, + 0.4577017426490784, + 0.0630958378314972 + ], + [ 0.2382799535989761, + 0.9706341028213501, + 0.9022080898284912, + 0.8509197831153870 + ], + + [ 0.2666657567024231, + 0.5397603511810303, + 0.3752069771289825, + 0.7602487206459045 + ], + [ 0.5125353932380676, + 0.6677237749099731, + 0.5316064357757568, + 0.0392803438007832 + ] + ] +})json"; + +static const std::string_view json_attention_v_weights = + R"json({ + "tensor": [ + [ 0.4376375973224640, + 0.9318350553512573, + 0.9308097958564758, + 0.7209523320198059 + ], + [ 0.2842934131622314, + 0.7385343313217163, + 0.6399788260459900, + 0.3540486693382263 + ], + + [ 0.6878613829612732, + 0.1659741699695587, + 0.4401045143604279, + 0.8800752162933350 + ], + [ 0.8292011022567749, + 0.3303371369838715, + 0.2289681732654572, + 0.8933724164962769 + ] + ] +})json"; + +static const std::string_view json_attention_fwd_final_linear_transform_weights = + R"json({ + "tensor": [ + [ 0.3503601849079132, + 0.6866698861122131, + 0.9564682245254517, + 0.5886401534080505 + ], + [ 0.6573040485382080, + 0.8586763143539429, + 0.4395599067211151, + 0.9239698052406311 + ], + [ 0.3984366655349731, + 0.8147668838500977, + 0.6842185258865356, + 0.9109720587730408 + ], + [ 0.4824906587600708, + 0.2158249616622925, + 0.9502523541450500, + 0.9201282262802124 + ] + ] +})json"; + +static const std::string_view json_dO_val = + R"json({ + "tensor": [ + [0.4326967000961304, 0.5034142732620239], + [0.3742998838424683, 0.8130495548248291], + [0.2719697952270508, 0.0869214236736298], + [0.0300454571843147, 0.5597561597824097], + [0.6639074087142944, 0.1598965376615524], + [0.9877028465270996, 0.4654746651649475], + [0.1985244303941727, 0.7646803855895996], + [0.3851194083690643, 0.9054278135299683], + [0.7841265201568604, 0.2662073373794556], + [0.0736359953880310, 0.0607693009078503], + [0.0075395158492029, 0.6795202493667603], + [0.1296833753585815, 0.2451221495866776], + [0.0392608530819416, 0.3937692046165466], + [0.5831758975982666, 0.8619759082794189], + [0.3022827506065369, 0.1364451646804810], + [0.3314520418643951, 0.5866937041282654], + [0.6233373880386353, 0.5215975046157837], + [0.7075114250183105, 0.8668782114982605], + [0.0853137969970703, 0.0257864110171795], + [0.6712992787361145, 0.3348474800586700] + ] +})json"; + +static const std::string_view json_str_dV = R"json({ + "tensor": + [ + [ + 0.46276405453681946, + 0.5632290840148926, + 0.6301558017730713, + 0.6143999695777893 + ], + [ + 0.21040800213813782, + 0.23169182240962982, + 0.38223886489868164, + 0.40467533469200134 + ], + [ + 0.2607715427875519, + 0.2960909307003021, + 0.3691549599170685, + 0.393097460269928 + ], + [ + 0.6277356743812561, + 0.7965565919876099, + 0.6256253123283386, + 0.6110870242118835 + ], + [ + 0.21125063300132751, + 0.2354714274406433, + 0.4219350814819336, + 0.4393003582954407 + ], + + [ + 0.132674440741539, + 0.3022109270095825, + 0.320855051279068, + 0.32227981090545654 + ], + [ + 0.09208431094884872, + 0.21530890464782715, + 0.29005980491638184, + 0.2968984544277191 + ], + [ + 0.27132707834243774, + 0.5833349227905273, + 0.6035137176513672, + 0.5730515718460083 + ], + [ + 0.2368224859237671, + 0.5134046673774719, + 0.50053870677948, + 0.48598945140838623 + ], + [ + 0.32903170585632324, + 0.7025806307792664, + 0.7039426565170288, + 0.6575907468795776 + ] + ] +})json"; + +static const std::string_view json_str_dQ = R"json({ + "tensor": + [ + [ + 0.028277257457375526, + 0.07200080156326294, + 0.041350290179252625, + 0.03680729493498802 + ], + [ + 0.03550948202610016, + 0.09047053754329681, + 0.022952184081077576, + 0.021599717438220978 + ], + [ + 0.013200974091887474, + 0.0331602618098259, + 0.03173384815454483, + 0.029656197875738144 + ], + [ + 0.014150556176900864, + 0.03654339164495468, + 0.030366968363523483, + 0.026921339333057404 + ], + [ + 0.03096843883395195, + 0.07780992984771729, + 0.003662070259451866, + 0.0032906262204051018 + ], + + + [ + 0.01230604387819767, + 0.08554355800151825, + 0.12323366105556488, + 0.1049455925822258 + ], + [ + 0.010758176445960999, + 0.061150021851062775, + 0.15029355883598328, + 0.12868845462799072 + ], + [ + 0.005017941817641258, + 0.046435996890068054, + 0.18252424895763397, + 0.15568195283412933 + ], + [ + 0.025845274329185486, + 0.19355353713035583, + 0.012176983058452606, + 0.01042320765554905 + ], + [ + 0.007626548409461975, + 0.059891343116760254, + 0.09300088882446289, + 0.08080047369003296 + ] + ] +})json"; + +static const std::string_view json_str_dK = R"json({ + "tensor": + [ + + [ + 0.12819401919841766, + 0.11493054777383804, + 0.18875014781951904, + 0.27348482608795166 + ], + [ + -0.18670454621315002, + -0.16970974206924438, + 0.035735923796892166, + 0.051827192306518555 + ], + [ + -0.170164093375206, + -0.15484140813350677, + -0.17655421793460846, + -0.2559382915496826 + ], + [ + 0.36930567026138306, + 0.33704400062561035, + 0.06264787912368774, + 0.0910910815000534 + ], + [ + -0.14063072204589844, + -0.12742310762405396, + -0.11057973653078079, + -0.1604647934436798 + ], + + + [ + -0.18692390620708466, + -0.1738758087158203, + -0.11495179682970047, + -0.15318563580513 + ], + [ + -0.1666441559791565, + -0.15434391796588898, + -0.22044941782951355, + -0.29630666971206665 + ], + [ + 0.06872744113206863, + 0.06314821541309357, + 0.2631801664829254, + 0.3502889573574066 + ], + [ + -0.023755840957164764, + -0.022147303447127342, + -0.015536848455667496, + -0.020632755011320114 + ], + [ + 0.30859625339508057, + 0.2872186303138733, + 0.08775784820318222, + 0.11983604729175568 + ] + ] +})json"; diff --git a/test/gtest/cache.cpp b/test/gtest/cache.cpp index 3163ddafd0..a231d53d18 100644 --- a/test/gtest/cache.cpp +++ b/test/gtest/cache.cpp @@ -25,16 +25,18 @@ *******************************************************************************/ #include +#include #include #include - +#include +#include #include "test.hpp" #include "random.hpp" #include #if MIOPEN_ENABLE_SQLITE -std::string random_string(size_t length) +std::vector random_bytes(size_t length) { auto randchar = []() -> char { const char charset[] = "0123456789" @@ -43,20 +45,20 @@ std::string random_string(size_t length) const size_t max_index = (sizeof(charset) - 1); return charset[prng::gen_0_to_B(max_index)]; }; - std::string str(length, 0); - std::generate_n(str.begin(), length, randchar); - return str; + std::vector v(length, 0); + std::generate_n(v.begin(), length, randchar); + return v; } TEST(TestCache, check_bz2_compress) { - std::string to_compress; + std::vector to_compress; bool success = false; - std::string cmprsd; + std::vector cmprsd; EXPECT_TRUE(throws([&]() { cmprsd = miopen::compress(to_compress, &success); })); - to_compress = random_string(4096); + to_compress = random_bytes(4096); // if the following throws the test will fail cmprsd = miopen::compress(to_compress, nullptr); ASSERT_TRUE(!(cmprsd.empty())); @@ -67,41 +69,41 @@ TEST(TestCache, check_bz2_compress) TEST(TestCache, check_bz2_decompress) { - std::string empty_string; + std::vector empty; - std::string decompressed_str; + std::vector decompressed; - EXPECT_TRUE(throws([&]() { decompressed_str = miopen::decompress(empty_string, 0); })); + EXPECT_TRUE(throws([&]() { decompressed = miopen::decompress(empty, 0); })); - auto orig_str = random_string(4096); + auto original = random_bytes(4096); bool success = false; - std::string compressed_str; - compressed_str = miopen::compress(orig_str, &success); + std::vector compressed; + compressed = miopen::compress(original, &success); ASSERT_TRUE(success); - decompressed_str = miopen::decompress(compressed_str, orig_str.size()); - ASSERT_TRUE(decompressed_str == orig_str); + decompressed = miopen::decompress(compressed, original.size()); + ASSERT_TRUE(decompressed == original); - EXPECT_TRUE(throws([&]() { decompressed_str = miopen::decompress(compressed_str, 10); })); + EXPECT_TRUE(throws([&]() { decompressed = miopen::decompress(compressed, 10); })); - ASSERT_TRUE(decompressed_str == miopen::decompress(compressed_str, orig_str.size() + 10)); + ASSERT_TRUE(decompressed == miopen::decompress(compressed, original.size() + 10)); } TEST(TestCache, check_kern_db) { miopen::KernelConfig cfg0; cfg0.kernel_name = "kernel1"; - cfg0.kernel_args = random_string(512); - cfg0.kernel_blob = random_string(8192); + cfg0.kernel_args = {random_bytes(512).data(), 512}; + cfg0.kernel_blob = random_bytes(8192); - miopen::KernDb empty_db("", false); + miopen::KernDb empty_db(miopen::DbKinds::KernelDb, "", false); EXPECT_TRUE(empty_db.RemoveRecordUnsafe(cfg0)); // for empty file, remove should succeed EXPECT_FALSE(empty_db.FindRecordUnsafe(cfg0)); // no record in empty database EXPECT_FALSE(empty_db.StoreRecordUnsafe(cfg0)); // storing in an empty database should fail { miopen::TempFile temp_file("tmp-kerndb"); - miopen::KernDb clean_db(std::string(temp_file), false); + miopen::KernDb clean_db(miopen::DbKinds::KernelDb, std::string(temp_file), false); EXPECT_TRUE(clean_db.StoreRecordUnsafe(cfg0)); auto readout = clean_db.FindRecordUnsafe(cfg0); @@ -114,16 +116,14 @@ TEST(TestCache, check_kern_db) { miopen::TempFile temp_file("tmp-kerndb"); miopen::KernDb err_db( + miopen::DbKinds::KernelDb, std::string(temp_file), false, - [](std::string str, bool* success) { - std::ignore = str; - *success = false; - return ""; + [](const std::vector&, bool* success) { + *success = false; + return std::vector{}; }, - [](std::string str, unsigned int sz) -> std::string { - std::ignore = str; - std::ignore = sz; + [](const std::vector&, unsigned int) -> std::vector { throw; }); // error compressing // Even if compression fails, it should still work @@ -138,5 +138,5 @@ TEST(TestCache, check_kern_db) TEST(TestCache, check_cache_file) { auto p = miopen::GetCacheFile("gfx", "base", "args"); - EXPECT_TRUE(p.filename().string() == "base.o"); + EXPECT_TRUE(p.filename() == "base.o"); } diff --git a/test/gtest/cat.hpp b/test/gtest/cat.hpp index 51e01ed69c..a4bf24a42a 100644 --- a/test/gtest/cat.hpp +++ b/test/gtest/cat.hpp @@ -100,15 +100,6 @@ std::vector CatTestConfigs() // clang-format on } -inline int32_t SetTensorLayout(miopen::TensorDescriptor& desc) -{ - std::vector lens = desc.GetLengths(); - std::vector int32_t_lens(lens.begin(), lens.end()); - - // set the strides for the tensor - return SetTensorNd(&desc, int32_t_lens, desc.GetType()); -} - template struct CatTest : public ::testing::TestWithParam { @@ -127,12 +118,10 @@ struct CatTest : public ::testing::TestWithParam for(auto in_dim : in_dims) { inputs.push_back(tensor{in_dim}.generate(gen_value)); - SetTensorLayout(inputs.back().desc); out_dim[dim] += in_dim[dim]; } output = tensor{out_dim}; - SetTensorLayout(output.desc); std::fill(output.begin(), output.end(), std::numeric_limits::quiet_NaN()); ref_output = tensor{out_dim}; diff --git a/test/gtest/cba_find2_infer.cpp b/test/gtest/cba_find2_infer.cpp index 0472688a50..062a768000 100644 --- a/test/gtest/cba_find2_infer.cpp +++ b/test/gtest/cba_find2_infer.cpp @@ -103,6 +103,8 @@ void RunTunableSolver(miopen::FusedProblem& problem, handle.Finish(); } +bool SkipTest() { return get_handle_xnack(); } + } // namespace cba_find2_infer using namespace cba_find2_infer; @@ -136,6 +138,11 @@ TEST_P(ConvBiasActivFind2InferTestHalf, ConvCKIgemmFwdBiasActivFind2Fused) #if MIOPEN_BACKEND_HIP TEST_P(ConvBiasActivFind2InferTestFloatFusionFind, ConvBiasActivFind2Float_testFind) { + if(SkipTest()) + { + test_skipped = true; + GTEST_SKIP() << "Fusion does not support xnack"; + } miopen::solver::debug::TuningIterationScopedLimiter tuning_limit{5}; std::vector solutions; diff --git a/test/gtest/conv3d_test.cpp b/test/gtest/conv3d_test.cpp index 551832fbd0..334d94d0dc 100644 --- a/test/gtest/conv3d_test.cpp +++ b/test/gtest/conv3d_test.cpp @@ -27,7 +27,7 @@ #include #include #include -#include "../conv3d.cpp" +#include "../conv3d.hpp" #include "get_handle.hpp" MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) @@ -105,19 +105,19 @@ std::vector GetTestCases(const std::string& precision) const std::vector test_cases = { // clang-format off // test_conv3d_extra - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd0}, - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd1}, - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd2}, - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd3}, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd0}, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd1}, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd2}, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd3}, //test_conv3d_extra reduced set - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd0 }, - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd1 }, - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd2 }, - {precision + "--input 2 16 50 50 50 --weights 32 16 5 5 5" + psd3 }, - {precision + "--input 1 16 4 161 700 --weights 16 16 3 11 11" + psd4 }, - {precision + "--input 1 16 4 161 700 --weights 16 16 3 11 11" + psd5 }, - {precision + "--input 1 16 4 140 602 --weights 16 16 3 11 11" + psd4 }, - {precision + "--input 1 16 4 140 602 --weights 16 16 3 11 11" + psd5 } + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd0 }, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd1 }, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd2 }, + {precision + " --input 2 16 50 50 50 --weights 32 16 5 5 5" + psd3 }, + {precision + " --input 1 16 4 161 700 --weights 16 16 3 11 11" + psd4 }, + {precision + " --input 1 16 4 161 700 --weights 16 16 3 11 11" + psd5 }, + {precision + " --input 1 16 4 140 602 --weights 16 16 3 11 11" + psd4 }, + {precision + " --input 1 16 4 140 602 --weights 16 16 3 11 11" + psd5 } // clang-format on }; return test_cases; diff --git a/test/gtest/conv_3d.cpp b/test/gtest/conv_3d.cpp new file mode 100644 index 0000000000..8db5180b42 --- /dev/null +++ b/test/gtest/conv_3d.cpp @@ -0,0 +1,119 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../conv3d.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace conv_3d { +void GetArgs(const std::string& param, std::vector& tokens) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + while(begin != end) + tokens.push_back(*begin++); +} + +std::vector GetTestCases(void) +{ + std::string cmd_v = "test_conv3d --verbose "; + + // clang-format off + return std::vector{ + {cmd_v + " --conv_dim_type conv3d --input 16 32 4 9 9 --weights 64 32 3 3 3 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --group-count 1 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 4 3 4 227 227 --weights 4 3 3 11 11 --pads_strides_dilations 0 0 0 1 1 1 1 1 1 --group-count 1 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 16 128 4 56 56 --weights 256 4 3 3 3 --pads_strides_dilations 1 1 1 1 1 1 1 1 1 --group-count 32 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 16 128 56 56 56 --weights 256 4 3 3 3 --pads_strides_dilations 1 2 3 1 1 1 1 2 3 --group-count 32 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 4 4 4 161 700 --weights 32 1 3 5 20 --pads_strides_dilations 1 1 1 2 2 2 1 1 1 --group-count 4 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 8 512 4 28 28 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 1 1 1 1 1 --group-count 4 --cmode conv --pmode same"}, + {cmd_v + " --conv_dim_type conv3d --input 8 512 4 56 56 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --group-count 4 --cmode conv --pmode same"}, + {cmd_v + " --conv_dim_type conv3d --input 8 512 3 14 14 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --trans_output_pads 0 0 0 --group-count 1 --cmode trans --pmode same"}, + {cmd_v + " --conv_dim_type conv3d --input 16 64 3 4 4 --weights 64 32 1 3 3 --pads_strides_dilations 0 0 0 2 2 2 1 1 1 --trans_output_pads 0 0 0 --group-count 4 --cmode trans --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 16 32 4 9 9 --weights 64 32 3 3 3 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --group-count 1 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 4 3 4 227 227 --weights 4 3 3 11 11 --pads_strides_dilations 0 0 0 1 1 1 1 2 3 --group-count 1 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 16 128 4 56 56 --weights 256 4 3 3 3 --pads_strides_dilations 1 2 3 1 1 1 1 2 3 --group-count 32 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 4 4 4 161 700 --weights 32 1 3 5 20 --pads_strides_dilations 1 2 3 1 2 3 1 2 3 --group-count 4 --cmode conv --pmode default"}, + {cmd_v + " --conv_dim_type conv3d --input 8 512 4 28 28 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 1 1 1 2 3 --group-count 4 --cmode conv --pmode same"}, + {cmd_v + " --conv_dim_type conv3d --input 8 512 4 56 56 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --group-count 4 --cmode conv --pmode same"}, + {cmd_v + " --conv_dim_type conv3d --input 8 512 3 14 14 --weights 512 128 1 1 1 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --trans_output_pads 0 0 0 --group-count 1 --cmode trans --pmode same"}, + {cmd_v + " --conv_dim_type conv3d --input 16 64 3 4 4 --weights 64 32 1 3 3 --pads_strides_dilations 0 0 0 1 2 3 1 2 3 --trans_output_pads 0 0 0 --group-count 4 --cmode trans --pmode default"} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithFloat_conv_3d : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using namespace miopen::debug; + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() // + && (miopen::IsUnset(ENV(MIOPEN_TEST_ALL)) // standalone run + || (miopen::IsEnabled(ENV(MIOPEN_TEST_ALL)) // or --float full tests enabled + && miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--float")))) + { + GTEST_SKIP(); + } + std::vector params = ConfigWithFloat_conv_3d::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens; + GetArgs(test_value, tokens); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace conv_3d +using namespace conv_3d; + +TEST_P(ConfigWithFloat_conv_3d, FloatTest_conv_3d) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(Conv3d, ConfigWithFloat_conv_3d, testing::Values(GetTestCases())); diff --git a/test/gtest/conv_common.cpp b/test/gtest/conv_common.cpp new file mode 100644 index 0000000000..fd28fffc96 --- /dev/null +++ b/test/gtest/conv_common.cpp @@ -0,0 +1,35 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "conv_common.hpp" + +bool get_handle_xnack() +{ + auto& handle = get_handle(); + auto is_xnack_b = handle.GetTargetProperties().Xnack(); + bool is_xnack = (is_xnack_b) ? *is_xnack_b : false; + return is_xnack; +} diff --git a/test/gtest/conv_extra.cpp b/test/gtest/conv_extra.cpp new file mode 100644 index 0000000000..6cec3eaeb2 --- /dev/null +++ b/test/gtest/conv_extra.cpp @@ -0,0 +1,132 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../conv2d.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLAGS_ARGS) + +namespace conv_extra { +void GetArgs(const std::string& param, std::vector& tokens) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + while(begin != end) + tokens.push_back(*begin++); +} + +std::vector GetTestCases(void) +{ + std::string cmd = "test_conv2d "; + std::string v = " --verbose "; + std::string float_arg = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)); + std::string flag_arg = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLAGS_ARGS)); + + // clang-format off + return std::vector{ + // {cmd + v + " --input 1 1 1 1 --weights 1 1 2 2 --pads_strides_dilations 0 0 3 3 1 1"}, + {cmd + v + " --input 4 1 161 700 --weights 4 1 5 20 --pads_strides_dilations 0 0 2 2 1 1"}, + {cmd + v + " --input 4 1 161 700 --weights 4 1 5 20 --pads_strides_dilations 0 0 2 2 1 1"}, + {cmd + v + " --input 4 32 79 341 --weights 4 32 5 10 --pads_strides_dilations 0 0 2 2 1 1"}, + {cmd + v + " --input 4 32 79 341 --weights 4 32 5 10 --pads_strides_dilations 0 0 2 2 1 1"}, + {cmd + v + " --input 4 3 227 227 --weights 4 3 11 11 --pads_strides_dilations 0 0 4 4 1 1"}, + {cmd + v + " --input 4 3 224 224 --weights 4 3 11 11 --pads_strides_dilations 2 2 4 4 1 1"}, + {cmd + v + " --input 16 1 48 480 --weights 16 1 3 3 --pads_strides_dilations 1 1 1 1 1 1"}, + // Forward disabled since FFT fails verification for the forward direction + {cmd + v + " --input 32 64 27 27 --weights 192 64 5 5 --pads_strides_dilations 2 2 1 1 1 1 --disable-forward"}, + // {cmd + v + " --input 4 64 14 14 --weights 24 64 5 5 --pads_strides_dilations 2 2 1 1 1 1"}, + {cmd + v + " --input 4 96 14 14 --weights 32 96 5 5 --pads_strides_dilations 2 2 1 1 1 1"}, + {cmd + v + " --input 4 16 14 14 --weights 4 16 5 5 --pads_strides_dilations 2 2 1 1 1 1"}, + {cmd + v + " --input 4 32 14 14 --weights 4 32 5 5 --pads_strides_dilations 2 2 1 1 1 1"}, + + {cmd + float_arg + " --input 16 3 64 128 --weights 96 3 11 11 --pads_strides_dilations 0 0 1 1 1 1 " + flag_arg}, + {cmd + float_arg + " --input 16 3 32 32 --weights 96 3 11 11 --pads_strides_dilations 0 0 2 2 1 1 " + flag_arg}, + {cmd + float_arg + " --input 16 3 64 128 --weights 96 3 11 11 --pads_strides_dilations 5 5 2 2 1 1 " + flag_arg}, + {cmd + float_arg + " --input 16 3 32 32 --weights 96 3 11 11 --pads_strides_dilations 5 5 2 2 1 1 " + flag_arg}, + + {cmd + float_arg + " --input 2 16 1024 2048 --weights 32 16 3 3 --pads_strides_dilations 0 0 1 1 1 1 " + flag_arg}, + {cmd + float_arg + " --input 2 16 1024 2048 --weights 32 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 " + flag_arg}, + {cmd + float_arg + " --input 2 16 3072 3072 --weights 32 16 3 3 --pads_strides_dilations 0 0 2 2 1 1 " + flag_arg}, + {cmd + float_arg + " --input 2 16 3072 3072 --weights 32 16 3 3 --pads_strides_dilations 2 2 2 2 1 1 " + flag_arg}, + + {cmd + float_arg + " --input 128 320 1 7 --weights 256 320 1 1 --pads_strides_dilations 0 0 1 1 1 1 " + flag_arg}, + {cmd + float_arg + " --input 128 1024 1 7 --weights 2048 1024 1 1 --pads_strides_dilations 1 1 1 1 1 1 " + flag_arg}, + {cmd + float_arg + " --input 352 192 7 1 --weights 320 192 1 1 --pads_strides_dilations 0 0 1 1 1 1 " + flag_arg}, + {cmd + float_arg + " --input 352 16 7 1 --weights 32 16 1 1 --pads_strides_dilations 2 2 1 1 1 1 " + flag_arg} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithFloat_conv_extra : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using namespace miopen::debug; + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() && (miopen::IsUnset(ENV(MIOPEN_TEST_ALL))))) + { + GTEST_SKIP(); + } + std::vector params = ConfigWithFloat_conv_extra::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens; + GetArgs(test_value, tokens); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace conv_extra +using namespace conv_extra; + +TEST_P(ConfigWithFloat_conv_extra, FloatTest_conv_extra) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(ConvExtra, ConfigWithFloat_conv_extra, testing::Values(GetTestCases())); diff --git a/test/gtest/conv_group.cpp b/test/gtest/conv_group.cpp new file mode 100644 index 0000000000..a8f7e82331 --- /dev/null +++ b/test/gtest/conv_group.cpp @@ -0,0 +1,155 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../conv2d.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace conv_group { +void GetArgs(const std::string& param, std::vector& tokens) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + while(begin != end) + tokens.push_back(*begin++); +} + +std::vector GetTestCases(void) +{ + std::string cmd_v = "test_conv2d --verbose "; + + // clang-format off + return std::vector{ + {cmd_v + " --input 16 128 56 56 --weights 256 4 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 16 256 56 56 --weights 512 8 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32"}, + {cmd_v + " --input 16 256 28 28 --weights 512 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 16 512 28 28 --weights 1024 16 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32"}, + {cmd_v + " --input 16 512 14 14 --weights 1024 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 16 1024 14 14 --weights 2048 32 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32"}, + {cmd_v + " --input 16 1024 7 7 --weights 2048 32 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 32 128 56 56 --weights 256 4 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 32 256 56 56 --weights 512 8 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32"}, + // + // Workaround for "Memory access fault by GPU node" during "HIP Release All" - WrW disabled. + {cmd_v + " --input 32 256 28 28 --weights 512 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32 --disable-backward-weights"}, + {cmd_v + " -input 32 512 28 28 --weights 1024 16 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32"}, + {cmd_v + " --input 32 512 14 14 --weights 1024 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 32 1024 14 14 --weights 2048 32 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 32"}, + {cmd_v + " --input 32 1024 7 7 --weights 2048 32 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 32"}, + {cmd_v + " --input 4 4 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 4"}, + {cmd_v + " --input 8 2 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2"}, + {cmd_v + " --input 16 4 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 4"}, + {cmd_v + " --input 32 2 161 700 --weights 32 1 5 20 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2"}, + {cmd_v + " --input 4 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2"}, + {cmd_v + " --input 8 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2"}, + {cmd_v + " --input 16 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2"}, + {cmd_v + " --input 32 32 79 341 --weights 32 16 5 10 --pads_strides_dilations 0 0 2 2 1 1 --group-count 2"}, + {cmd_v + " --input 16 4 48 480 --weights 16 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4"}, + {cmd_v + " --input 16 16 24 240 --weights 32 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 16"}, + {cmd_v + " --input 16 32 12 120 --weights 64 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4"}, + {cmd_v + " --input 16 64 6 60 --weights 128 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4"}, + {cmd_v + " --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 3"}, + {cmd_v + " --input 8 64 54 54 --weights 64 8 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 8"}, + {cmd_v + " --input 8 128 27 27 --weights 128 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 8"}, + {cmd_v + " --input 8 3 224 224 --weights 63 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 3"}, + {cmd_v + " --input 8 64 112 112 --weights 128 32 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 2"}, + {cmd_v + " --input 16 9 224 224 --weights 63 3 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 3"}, + // + // Workaround for "Memory access fault by GPU node" during "FP32 gfx908 Hip Release All subset" - WrW disabled. + {cmd_v + " --input 16 64 112 112 --weights 128 16 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 4 --disable-backward-weights"}, + {cmd_v + " --input 16 3 224 224 --weights 63 1 7 7 --pads_strides_dilations 3 3 2 2 1 1 --group-count 3"}, + {cmd_v + " --input 16 192 28 28 --weights 32 12 5 5 --pads_strides_dilations 2 2 1 1 1 1 --group-count 16"}, + {cmd_v + " --input 16 832 7 7 --weights 128 52 5 5 --pads_strides_dilations 2 2 1 1 1 1 --group-count 16"}, + {cmd_v + " --input 16 192 28 28 --weights 32 24 1 1 --pads_strides_dilations 0 0 1 1 1 1 --group-count 8"}, + {cmd_v + " --input 16 832 7 7 --weights 128 104 1 1 --pads_strides_dilations 0 0 1 1 1 1 --group-count 8"}, + {cmd_v + " --input 11 23 161 700 --weights 46 1 7 7 --pads_strides_dilations 1 1 2 2 1 1 --group-count 23"}, + {cmd_v + " --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 7"}, + {cmd_v + " --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 0 0 1 1 1 1 --group-count 7"}, + {cmd_v + " --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 0 0 2 2 1 1 --group-count 7"}, + {cmd_v + " --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 7"}, + {cmd_v + " --input 8 7 224 224 --weights 63 1 3 3 --pads_strides_dilations 2 2 2 2 1 1 --group-count 7"}, + {cmd_v + " --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 1 1 1 1 1 1 --group-count 3"}, + {cmd_v + " --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 0 0 1 1 1 1 --group-count 3"}, + {cmd_v + " --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 0 0 2 2 1 1 --group-count 3"}, + {cmd_v + " --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 1 1 2 2 1 1 --group-count 3"}, + {cmd_v + " --input 8 3 108 108 --weights 63 1 3 3 --pads_strides_dilations 2 2 2 2 1 1 --group-count 3"} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithFloat_conv_group : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using namespace miopen::debug; + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() // + && (miopen::IsUnset(ENV(MIOPEN_TEST_ALL)) // standalone run + || (miopen::IsEnabled(ENV(MIOPEN_TEST_ALL)) // or --float full tests enabled + && miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--float")))) + { + GTEST_SKIP(); + } + std::vector params = ConfigWithFloat_conv_group::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens; + GetArgs(test_value, tokens); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace conv_group +using namespace conv_group; + +TEST_P(ConfigWithFloat_conv_group, FloatTest_conv_group) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(ConvGroup, ConfigWithFloat_conv_group, testing::Values(GetTestCases())); diff --git a/test/gtest/conv_igemm_dynamic.cpp b/test/gtest/conv_igemm_dynamic.cpp index 8bc9390e4e..f5b502fed1 100644 --- a/test/gtest/conv_igemm_dynamic.cpp +++ b/test/gtest/conv_igemm_dynamic.cpp @@ -31,7 +31,6 @@ #include "../conv2d.hpp" MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) namespace conv_igemm_dynamic { @@ -111,7 +110,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } class Conv2dFloatDynamic : public FloatTestCase> { diff --git a/test/gtest/conv_igemm_dynamic_dlops.cpp b/test/gtest/conv_igemm_dynamic_dlops.cpp index 98ca156236..4a3457c812 100644 --- a/test/gtest/conv_igemm_dynamic_dlops.cpp +++ b/test/gtest/conv_igemm_dynamic_dlops.cpp @@ -31,7 +31,6 @@ #include "../conv2d.hpp" MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) namespace { @@ -146,11 +145,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() -{ - return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)) || - miopen::IsDisabled(ENV(MIOPEN_TEST_ALL)); -} +bool SkipTest() { return get_handle_xnack() || miopen::IsDisabled(ENV(MIOPEN_TEST_ALL)); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/conv_tensor_gen.hpp b/test/gtest/conv_tensor_gen.hpp index 572480123d..83b40b860a 100644 --- a/test/gtest/conv_tensor_gen.hpp +++ b/test/gtest/conv_tensor_gen.hpp @@ -26,43 +26,26 @@ #pragma once #include - -#include - #include -// Copied from conv_driver.hpp +#include "../random.hpp" -template -inline T FRAND() -{ - double d = static_cast(rand() / (static_cast(RAND_MAX))); - return static_cast(d); -} - -template -inline T RAN_GEN(T A, T B) -{ - T r = (FRAND() * (B - A)) + A; - return r; -} template inline T RanGenData() { - return RAN_GEN(static_cast(0.0f), static_cast(1.0f)); + return prng::gen_canonical(); } template <> inline float8 RanGenData() { - return RAN_GEN(static_cast(-1.0f), static_cast(1.0f)); + return prng::gen_A_to_B(static_cast(-1.0f), static_cast(1.0f)); } template <> inline bfloat8 RanGenData() { - const auto tmp = RAN_GEN(static_cast(-1.0f), static_cast(1.0f)); - return static_cast(tmp); + return prng::gen_A_to_B(static_cast(-1.0f), static_cast(1.0f)); } template @@ -78,7 +61,7 @@ struct GenData template T RanGenWeights() { - return RAN_GEN(static_cast(-0.5), static_cast(0.5)); + return prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); } // Shift FP16 distribution towards positive numbers, @@ -86,34 +69,28 @@ T RanGenWeights() template <> inline half_float::half RanGenWeights() { - return RAN_GEN(static_cast(-1.0 / 3.0), - static_cast(0.5)); + return prng::gen_A_to_B(static_cast(-1.0 / 3.0), + static_cast(0.5)); } template <> inline float8 RanGenWeights() { - const auto tmp = - RAN_GEN(0.0, 1.0) > 0.5 ? static_cast(0.0) : static_cast(1.0); + const auto tmp = prng::gen_canonical() > 0.5f ? 0.0f : 2.0f; // 1 in 2 chance of number being positive - const float sign = - (RAN_GEN(0.0, 1.0) > 0.5) ? static_cast(-1) : static_cast(1); - const auto tmp2 = static_cast(std::numeric_limits::epsilon()) * - static_cast(2) * sign * static_cast(tmp); - return static_cast(tmp2); + const auto sign = prng::gen_canonical() > 0.5f ? -1.0f : 1.0f; + return static_cast(static_cast(std::numeric_limits::epsilon()) * sign * + tmp); } template <> inline bfloat8 RanGenWeights() { - const auto tmp = - RAN_GEN(0.0, 1.0) > 0.5 ? static_cast(0.0) : static_cast(1.0); + const auto tmp = prng::gen_canonical() > 0.5f ? 0.0f : 2.0f; // 1 in 2 chance of number being positive - const float sign = - (RAN_GEN(0.0, 1.0) > 0.5) ? static_cast(-1) : static_cast(1); - const auto tmp2 = static_cast(std::numeric_limits::epsilon()) * - static_cast(2) * sign * static_cast(tmp); - return static_cast(tmp2); + const auto sign = prng::gen_canonical() > 0.5f ? -1.0f : 1.0f; + return static_cast(static_cast(std::numeric_limits::epsilon()) * sign * + tmp); } template diff --git a/test/gtest/cpu_multi_head_attention.cpp b/test/gtest/cpu_multi_head_attention.cpp new file mode 100644 index 0000000000..bf741764f1 --- /dev/null +++ b/test/gtest/cpu_multi_head_attention.cpp @@ -0,0 +1,21 @@ +#include "cpu_multi_head_attention.hpp" + +struct CPUMHATestFloat : test::cpu::CPUMHATest +{ +}; + +struct CPUMHATestFloat8 : test::cpu::CPUMHATest +{ +}; + +TEST_P(CPUMHATestFloat, CPUMHATestFloatFw) {} + +TEST_P(CPUMHATestFloat8, CPUMHATestFloat8Fw) {} + +INSTANTIATE_TEST_SUITE_P(CPUMHATestSet, + CPUMHATestFloat, + testing::ValuesIn(test::cpu::CPUMHAConfigs())); + +INSTANTIATE_TEST_SUITE_P(CPUMHATestSet, + CPUMHATestFloat8, + testing::ValuesIn(test::cpu::CPUMHAConfigs())); diff --git a/test/gtest/cpu_multi_head_attention.hpp b/test/gtest/cpu_multi_head_attention.hpp new file mode 100644 index 0000000000..52fc8bcde6 --- /dev/null +++ b/test/gtest/cpu_multi_head_attention.hpp @@ -0,0 +1,361 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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. + * + *******************************************************************************/ +#pragma once + +#include "mha_helper.hpp" +#include "attention_golden.hpp" +#include "verify.hpp" + +namespace test { +namespace cpu { + +template +void check(const std::string_view& json_data, + const tensor& computed_tensor, + const double& ethreshold = 1e-7, + const double& mdthreshold = 1e-6) +{ + auto calcStats = [ref_tensor_val = ExtractGoldenDataFromJson(json_data, computed_tensor)]( + const auto& tensor_val) { + return std::tuple{miopen::rms_range(ref_tensor_val, tensor_val), + miopen::max_diff(ref_tensor_val, tensor_val)}; + }; + + const auto [error, max_diff] = calcStats(computed_tensor); + // CI clang-tidy treats is as "boolean value assigned to float" + const double error_threshold = ((std::is_same_v) ? ethreshold : 5e-2); + const double max_diff_threshold = ((std::is_same_v) ? mdthreshold : 5e-1); + EXPECT_LT(error, error_threshold); + EXPECT_LT(max_diff, max_diff_threshold); +} + +std::vector CPUMHAConfigs() +{ + // clang-format off + return {{// batch_size, sequence_length, num_heads, problem_dimension, drop_out_rate + 2, 5, 2, 4, 0.0}}; + // clang-format on +} + +template +struct CPUMHATest : public ::testing::TestWithParam +{ +protected: + void SetUp() override + { + cpu_mha_test_case = GetParam(); + + // Initialize the tensors + init(); + // fp32 Q, K, V + Dot_3D_3D_T(word_position, q_weights, q_val); + Dot_3D_3D_T(word_position, k_weights, k_val); + Dot_3D_3D_T(word_position, v_weights, v_val); + + float sqr_dk = std::sqrt(q_val.desc.GetLengths()[3]); + ScaleMult(q_val, 1.0f / sqr_dk, q_val); + + if constexpr(std::is_same_v) + { + // forward + MultiHeadAttentionf32(q_val, + k_val, + v_val, + q_dot_k_transpose, + softmax, + attn_max, + z_sum, + aMax_S, + aMax_O, + multi_head_attention); + + Concat(multi_head_attention, concatinated_attention); + + // backward + MultiHeadAttentionBackwardDataf32(q_val, + k_val, + v_val, + multi_head_attention, // o_val + dO_val, + softmax, + attn_max, + z_sum, + dQ_val, + dK_val, + dV_val); + + Concat(dO_val, concatinated_dO_val); + Concat(dV_val, concatinated_dV_val); + Concat(dQ_val, concatinated_dQ_val); + Concat(dK_val, concatinated_dK_val); + } + else + { + float q_scale = GetF8Scaling(AbsoluteMax(q_val)); + float k_scale = GetF8Scaling(AbsoluteMax(k_val)); + float v_scale = GetF8Scaling(AbsoluteMax(v_val)); + float q_descale = 1.f / q_scale; + float k_descale = 1.f / k_scale; + float v_descale = 1.f / v_scale; + + float s_scale = 1.f; + // clang-tidy complains about the same expression on both sides of "/": 1.f / 1.f + float s_descale = 1.f; // / s_scale; + + float o_scale = 1.f; + // clang-tidy complains about the same expression on both sides of "/": 1.f / 1.f + float o_descale = 1.f; // / o_scale; + + tensor q_val_fp8(q_val.desc.GetLengths()); + tensor k_val_fp8(k_val.desc.GetLengths()); + tensor v_val_fp8(v_val.desc.GetLengths()); + + ScaleMult(q_val, q_scale, q_val_fp8); + ScaleMult(k_val, k_scale, k_val_fp8); + ScaleMult(v_val, v_scale, v_val_fp8); + + // forward + MultiHeadAttentionfp8(q_val_fp8, + k_val_fp8, + v_val_fp8, + softmax, // fp32 + attn_max, + z_sum, + q_descale, + k_descale, + v_descale, + s_descale, + s_scale, + o_scale, + 0.0f, + 0, + 0, + aMax_S, + aMax_O, + multi_head_attention); + Concat(multi_head_attention, final_transformed_attention); + ScaleMult(final_transformed_attention, o_descale, concatinated_attention); + + // backward + float dO_scale = GetF8Scaling(AbsoluteMax(dO_val)); + float dO_descale = 1.f / dO_scale; + + float dV_scale = 1.f; + float dK_scale = 1.f; + float dQ_scale = 1.f; + + float dS_scale = 1.f; + // clang-tidy complains about the same expression on both sides of "/": 1.f / 1.f + float dS_descale = 1.f; // / dS_scale; + + tensor dO_val_fp8(dO_val.desc.GetLengths()); + + ScaleMult(dO_val, dO_scale, dO_val_fp8); + MultiHeadAttentionBackwardDataf8(q_val_fp8, + k_val_fp8, + v_val_fp8, + multi_head_attention, // O_val_fp8 + dO_val_fp8, + softmax, + q_descale, + k_descale, + v_descale, + dQ_scale, + dK_scale, + dV_scale, + s_scale, + s_descale, + dS_scale, + dS_descale, + o_descale, + dO_descale, + aMax_dS, + aMax_dQ, + aMax_dK, + aMax_dV, + dQ_val, + dK_val, + dV_val); + } + + Dot_3D_2D_T( + concatinated_attention, final_linear_transform_weights, final_transformed_attention); + } + + void TearDown() override + { + check(json_attention_fwd_golden_data, final_transformed_attention); + if constexpr(std::is_same_v) + { + check(json_str_dV, concatinated_dV_val, 1e-5, 1e-4); + check(json_str_dQ, concatinated_dQ_val, 1e-5, 1e-4); + check(json_str_dK, concatinated_dK_val, 1e-5, 1e-4); + } + } + + void init() + { + d_k = cpu_mha_test_case.problem_dimension / cpu_mha_test_case.num_heads; + + // mask = tensor{ + // std::vector{cpu_mha_test_case.sequence_length, + // cpu_mha_test_case.sequence_length}}; + // SetupMask(mask); + + word_position = tensor{std::vector{cpu_mha_test_case.batch_size, + cpu_mha_test_case.sequence_length, + cpu_mha_test_case.problem_dimension}}; + + // since Pytorch's Y = X*W_tranpose + // cpu_mha_test_case.num_heads, cpu_mha_test_case.problem_dimension, d_k + // need to change the dimension to + // cpu_mha_test_case.num_heads, d_k, cpu_mha_test_case.problem_dimension + + q_weights = tensor(std::vector{cpu_mha_test_case.num_heads, // + d_k, // + cpu_mha_test_case.problem_dimension}); // + k_weights = q_weights; + v_weights = q_weights; + + q_val = tensor{cpu_mha_test_case.batch_size, + cpu_mha_test_case.num_heads, + cpu_mha_test_case.sequence_length, + d_k}; + k_val = q_val; + v_val = q_val; + + multi_head_attention = tensor{cpu_mha_test_case.batch_size, + cpu_mha_test_case.num_heads, + cpu_mha_test_case.sequence_length, + d_k}; + + final_linear_transform_weights = + tensor(std::vector{cpu_mha_test_case.problem_dimension, // + cpu_mha_test_case.problem_dimension}); // + + concatinated_attention = tensor{std::vector{ + cpu_mha_test_case.batch_size, + cpu_mha_test_case.sequence_length, + cpu_mha_test_case.problem_dimension}}; // cpu_mha_test_case.num_heads*d_k + final_transformed_attention = concatinated_attention; + + q_dot_k_transpose = tensor{cpu_mha_test_case.batch_size, + cpu_mha_test_case.num_heads, + cpu_mha_test_case.sequence_length, + cpu_mha_test_case.sequence_length}; + softmax = tensor{cpu_mha_test_case.batch_size, + cpu_mha_test_case.num_heads, + cpu_mha_test_case.sequence_length, + cpu_mha_test_case.sequence_length}; + // reduce row max + attn_max = tensor{cpu_mha_test_case.batch_size, + cpu_mha_test_case.num_heads, + cpu_mha_test_case.sequence_length, + 1}; + z_sum = attn_max; + + word_position = ExtractGoldenDataFromJson(json_attention_word_position, word_position); + q_weights = ExtractGoldenDataFromJson(json_attention_q_weights, q_weights); + k_weights = ExtractGoldenDataFromJson(json_attention_k_weights, k_weights); + v_weights = ExtractGoldenDataFromJson(json_attention_v_weights, v_weights); + + final_linear_transform_weights = ExtractGoldenDataFromJson( + json_attention_fwd_final_linear_transform_weights, final_linear_transform_weights); + + // backward + dO_val = v_val; + + dQ_val = tensor{cpu_mha_test_case.batch_size, + cpu_mha_test_case.num_heads, + cpu_mha_test_case.sequence_length, + d_k}; + dK_val = dQ_val; + dV_val = dQ_val; + // the derivative of loss (delta loss) + dO_val = ExtractGoldenDataFromJson(json_dO_val, dO_val); + concatinated_dO_val = concatinated_attention; + concatinated_dV_val = concatinated_attention; + concatinated_dQ_val = concatinated_attention; + concatinated_dK_val = concatinated_attention; + // backward + } + + CPUMHATestCase cpu_mha_test_case; + + // input + tensor word_position; + + size_t d_k; + + // weights + tensor q_weights; + tensor k_weights; + tensor v_weights; + // This for the final linear transformation + // of the attention. + tensor final_linear_transform_weights; + + // QKV vectors + tensor q_val; + tensor k_val; + tensor v_val; + + // softmax + tensor q_dot_k_transpose; + tensor softmax; + tensor attn_max; + tensor z_sum; + + // attention + tensor multi_head_attention; + + tensor concatinated_attention; + tensor final_transformed_attention; + + // scales + float aMax_S; + float aMax_O; + + // backward + + float aMax_dS; + float aMax_dK; + float aMax_dQ; + float aMax_dV; + tensor dO_val; + + tensor dQ_val; + tensor dK_val; + tensor dV_val; + + tensor concatinated_dO_val; + tensor concatinated_dV_val; + tensor concatinated_dQ_val; + tensor concatinated_dK_val; +}; + +} // namespace cpu +} // namespace test diff --git a/test/gtest/db_sync.cpp b/test/gtest/db_sync.cpp index 24ff9d0d38..a287c73a9a 100644 --- a/test/gtest/db_sync.cpp +++ b/test/gtest/db_sync.cpp @@ -560,7 +560,7 @@ void CheckDynamicFDBEntry(size_t thread_index, for(const auto& kern : sol.construction_params) { std::string compile_options = kern.comp_options; - std::string program_file = kern.kernel_file + ".o"; + auto program_file = miopen::make_object_file_name(kern.kernel_file).string(); ASSERT_TRUE(!miopen::EndsWith(kern.kernel_file, ".mlir")) << "MLIR detected in dynamic solvers"; compile_options += " -mcpu=" + handle.GetDeviceName(); @@ -592,7 +592,8 @@ TEST(DBSync, DISABLED_DynamicFDBSync) SetupPaths(fdb_file_path, pdb_file_path, kdb_file_path, handle); miopen::CheckKDBObjects(kdb_file_path, "", ""); - const auto& find_db = miopen::ReadonlyRamDb::GetCached(fdb_file_path.string(), true); + const auto& find_db = + miopen::ReadonlyRamDb::GetCached(miopen::DbKinds::FindDb, fdb_file_path.string(), true); // assert that find_db.cache is not empty, since that indicates the file was not readable ASSERT_TRUE(!find_db.GetCacheMap().empty()) << "Find DB does not have any entries"; @@ -731,7 +732,8 @@ void CheckFDBEntry(size_t thread_index, { bool found = false; std::string compile_options = kern.comp_options; - std::string program_file = kern.kernel_file + ".o"; + auto program_file = + miopen::make_object_file_name(kern.kernel_file).string(); if(!miopen::EndsWith(kern.kernel_file, ".mlir")) { auto& handle = ctx.GetStream(); @@ -814,7 +816,8 @@ void StaticFDBSync(const std::string& arch, const size_t num_cu) // Warmup the kdb cache miopen::CheckKDBObjects(kdb_file_path, "", ""); #endif - const auto& find_db = miopen::ReadonlyRamDb::GetCached(fdb_file_path.string(), true); + const auto& find_db = + miopen::ReadonlyRamDb::GetCached(miopen::DbKinds::FindDb, fdb_file_path.string(), true); // assert that find_db.cache is not empty, since that indicates the file was not readable ASSERT_TRUE(!find_db.GetCacheMap().empty()) << "Find DB does not have any entries"; auto _ctx = miopen::ExecutionContext{}; diff --git a/test/gtest/fusion_test.cpp b/test/gtest/fusion_test.cpp index 6046fa7e75..986cea4baf 100644 --- a/test/gtest/fusion_test.cpp +++ b/test/gtest/fusion_test.cpp @@ -60,10 +60,19 @@ struct FusionSetArgTestFloat : FusionSetArgTest { }; +bool SkipTest() { return get_handle_xnack(); } + TEST_P(FusionSetArgTestFloat, TestSetArgApiCall) { // Original fusion_plan/args execution happens in cba_infer.cpp // Original is checked independently and not sequentially, prior to FusionTestSetArgTest. + + if(SkipTest()) + { + test_skipped = true; + GTEST_SKIP() << "Fusion does not support xnack"; + } + using cba_float = cba; auto&& handle = get_handle(); diff --git a/test/gtest/gpu_multi_head_attention.cpp b/test/gtest/gpu_multi_head_attention.cpp new file mode 100644 index 0000000000..e430dd9a32 --- /dev/null +++ b/test/gtest/gpu_multi_head_attention.cpp @@ -0,0 +1,348 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "get_handle.hpp" +#include "mha_helper.hpp" +#include "tensor_holder.hpp" +#include "verify.hpp" +#include "../workspace.hpp" + +#include +#include + +#include + +#include +#include +#include +#include + +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) + +using namespace miopen; + +bool CheckFloatArg(std::string_view arg) +{ + const std::string& tmp = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)); + return tmp.empty() || tmp == arg; +} + +struct TensorStruct +{ + template + TensorStruct(tensor&& val) : m_cpu_tensor(std::move(val)) + { + } + + TensorStruct(const TensorStruct&) = delete; + TensorStruct(TensorStruct&&) = default; + + TensorStruct& operator=(const TensorStruct&) = delete; + TensorStruct& operator=(TensorStruct&&) = default; + + ~TensorStruct() = default; + + std::variant, tensor> m_cpu_tensor; + Allocator::ManageDataPtr m_gpu_buffer; +}; + +struct TestCase +{ + size_t n; + size_t h; + size_t s; + size_t d; + float dropout; +}; + +std::vector GetSmokeTestCases() +{ + if(CheckFloatArg("--float")) + { + return { + {9, 8, 8, 8, 0.0f}, + {1, 2, 4, 5, 0.0f}, + {2, 1, 5, 4, 0.0f}, + {4, 2, 1, 3, 0.0f}, + {5, 3, 4, 1, 0.0f}, + {1, 2, 65, 5, 0.0f}, + {2, 1, 67, 4, 0.0f}, + {8, 7, 68, 1, 0.0f}, + {1, 2, 257, 5, 0.0f}, + {2, 1, 259, 4, 0.0f}, + {8, 7, 270, 1, 0.0f}, + {1, 1, 1, 1, 0.0f}, + {3, 5, 32, 7, 0.8f}, + {2, 2, 64, 128, 0.8f}, + {2, 1, 128, 4, 0.8f}, + {2, 7, 256, 31, 0.8f}, + }; + } + else + { + return {}; + } +} + +std::vector GetFullTestCases() +{ + if((miopen::IsUnset(ENV(MIOPEN_TEST_ALL)) || miopen::IsEnabled(ENV(MIOPEN_TEST_ALL))) && + CheckFloatArg("--float")) + { + return { + {3, 15, 2047, 15, 0.0f}, + {2049, 17, 7, 7, 0.0f}, + {3, 3, 257, 91, 0.0f}, + {11, 150, 255, 31, 0.0f}, + {9, 3, 129, 1023, 0.0f}, + {3, 15, 31, 2047, 0.0f}, + {2049, 17, 32, 7, 0.2f}, + {11, 150, 256, 31, 0.4f}, + }; + } + else + { + return {}; + } +} + +class Test_Fwd_Mha : public testing::TestWithParam +{ +protected: + void SetUp() override + { + prng::reset_seed(); + auto [n, h, s, d, drop] = GetParam(); + Handle& handle = get_handle(); + + mha_descriptor.SetParams(1); + ASSERT_EQ(miopenCreateMhaProblem(&problem, &mha_descriptor, miopenProblemDirectionForward), + miopenStatusSuccess); + + auto InitTensor = [this, &handle](miopenTensorArgumentId_t id, auto&& tensor) { + auto tmp = std::make_unique(std::move(tensor)); + std::visit( + [this, id, &handle, &gpu_buff = tmp->m_gpu_buffer](auto&& cpu_tensor) { + EXPECT_EQ(miopenSetProblemTensorDescriptor(problem, id, &cpu_tensor.desc), + miopenStatusSuccess); + + gpu_buff = handle.Write(cpu_tensor.data); + descVector.push_back(&(cpu_tensor.desc)); + }, + tmp->m_cpu_tensor); + + args.emplace_back(); + args.back().id = id; + // args.back().descriptor will be filled later + args.back().buffer = tmp->m_gpu_buffer.get(); + + tensors[id] = std::move(tmp); + }; + + auto GenScaledTensor = [](auto... nhsd) { + float bias = prng::gen_A_to_B(-3.0f, 3.0f); + auto val_full = tensor{nhsd...}.generate( + [bias](auto...) { return prng::gen_A_to_B(-2.5f + bias, 2.5f + bias); }); + auto val_scaled = tensor{nhsd...}; + float scale = test::cpu::GetF8Scaling(test::cpu::AbsoluteMax(val_full)); + float descale = 1.f / scale; + test::cpu::ScaleMult(val_full, scale, val_scaled); + return std::tuple{val_scaled, scale, descale}; + }; + + float q_scale; + float q_descale; + tensor q_val; + std::tie(q_val, q_scale, q_descale) = GenScaledTensor(n, h, s, d); + InitTensor(miopenTensorMhaQ, std::move(q_val)); + + float k_scale; + float k_descale; + tensor k_val; + std::tie(k_val, k_scale, k_descale) = GenScaledTensor(n, h, s, d); + InitTensor(miopenTensorMhaK, std::move(k_val)); + + float v_scale; + float v_descale; + tensor v_val; + std::tie(v_val, v_scale, v_descale) = GenScaledTensor(n, h, s, d); + InitTensor(miopenTensorMhaV, std::move(v_val)); + + float s_scale = 1.f; + // clang-tidy complains about the same expression on both sides of "/": 1.f / 1.f + float s_descale = 1.f; // / s_scale; + + float o_scale = 1.f; + // clang-tidy complains about the same expression on both sides of "/": 1.f / 1.f + + InitTensor(miopenTensorMhaDescaleQ, + tensor{1, 1, 1, 1}.generate([=](auto...) { return q_descale; })); + InitTensor(miopenTensorMhaDescaleK, + tensor{1, 1, 1, 1}.generate([=](auto...) { return k_descale; })); + InitTensor(miopenTensorMhaDescaleV, + tensor{1, 1, 1, 1}.generate([=](auto...) { return v_descale; })); + InitTensor(miopenTensorMhaDescaleS, + tensor{1, 1, 1, 1}.generate([=](auto...) { return s_descale; })); + InitTensor(miopenTensorMhaScaleS, + tensor{1, 1, 1, 1}.generate([=](auto...) { return s_scale; })); + InitTensor(miopenTensorMhaScaleO, + tensor{1, 1, 1, 1}.generate([=](auto...) { return o_scale; })); + + InitTensor(miopenTensorMhaDropoutProbability, + tensor{1, 1, 1, 1}.generate([rate = drop](auto...) { return rate; })); + InitTensor(miopenTensorMhaDropoutSeed, + tensor{1, 1, 1, 2}.generate([](auto...) { return 0; })); + InitTensor(miopenTensorMhaDropoutOffset, + tensor{1, 1, 1, 2}.generate([](auto...) { return 0; })); + + InitTensor(miopenTensorMhaO, tensor{n, h, s, d}); + InitTensor(miopenTensorMhaAmaxO, tensor{1, 1, 1, 1}); + InitTensor(miopenTensorMhaAmaxS, tensor{1, 1, 1, 1}); + InitTensor(miopenTensorMhaM, tensor{n, h, s, 1}); + InitTensor(miopenTensorMhaZInv, tensor{n, h, s, 1}); + + for(size_t i = 0; i < descVector.size(); ++i) + { + args[i].descriptor = &descVector[i]; + } + + tensor q_dot_k_transpose{n, h, s, s}; + + softmax_ref = tensor{n, h, s, s}; + oDesc_ref = tensor{n, h, s, d}; + mDesc_ref = tensor{n, h, s, 1}; + zInvDesc_ref = tensor{n, h, s, 1}; + + test::cpu::MultiHeadAttentionfp8( + std::get>(tensors[miopenTensorMhaQ]->m_cpu_tensor), + std::get>(tensors[miopenTensorMhaK]->m_cpu_tensor), + std::get>(tensors[miopenTensorMhaV]->m_cpu_tensor), + softmax_ref, + mDesc_ref, + zInvDesc_ref, + q_descale, + k_descale, + v_descale, + s_descale, + s_scale, + o_scale, + drop, + 0, + 0, + amaxS_ref, + amaxO_ref, + oDesc_ref); + } + + void TearDown() override { ASSERT_EQ(miopenDestroyProblem(problem), miopenStatusSuccess); } + + std::map> tensors; + std::vector descVector; + std::vector args; + + // ref data + tensor softmax_ref; + tensor oDesc_ref; + tensor mDesc_ref; + tensor zInvDesc_ref; + float amaxS_ref; + float amaxO_ref; + + MhaDescriptor mha_descriptor; + miopenProblem_t problem; +}; + +TEST_P(Test_Fwd_Mha, Test_float) +{ + Handle& handle = get_handle(); + + auto [n, h, s, d, drop] = GetParam(); + if((drop > 0.0f) && (s % handle.GetWavefrontWidth() != 0)) + { + GTEST_SKIP() << "CPU Dropout currently supprorts only fully occupied warps"; + } + + std::vector solutions(16); + std::size_t found; + + ASSERT_EQ( + miopenFindSolutions(&handle, problem, nullptr, solutions.data(), &found, solutions.size()), + miopenStatusSuccess); + ASSERT_GT(found, 0); + solutions.resize(found); + + size_t workspace_size = 0; + Workspace workspace; + + for(const auto& solution : solutions) + { + miopenGetSolutionWorkspaceSize(solution, &workspace_size); + workspace.resize(workspace_size); + + ASSERT_EQ( + miopenRunSolution( + &handle, solution, args.size(), args.data(), workspace.ptr(), workspace.size()), + miopenStatusSuccess); + + auto GetResult = [this, &handle](miopenTensorArgumentId_t id) { + auto& tensorStructPtr = tensors[id]; + auto& cpu_tensor = std::get>(tensorStructPtr->m_cpu_tensor); + + cpu_tensor.data = + handle.Read(tensorStructPtr->m_gpu_buffer, cpu_tensor.data.size()); + + return cpu_tensor; + }; + + const double error_threshold = 5e-6; + + const auto& resAmaxS = GetResult(miopenTensorMhaAmaxS); + auto amaxS_abs_diff = std::abs(amaxS_ref - resAmaxS[0]); + EXPECT_LT(amaxS_abs_diff, error_threshold) + << " ref: " << amaxS_ref << " result: " << resAmaxS[0]; + + const auto& resAmaxO = GetResult(miopenTensorMhaAmaxO); + auto amaxO_abs_diff = std::abs(amaxO_ref - resAmaxO[0]); + EXPECT_LT(amaxO_abs_diff, error_threshold) + << " ref: " << amaxO_ref << " result: " << resAmaxO[0]; + + double M_error = miopen::rms_range(mDesc_ref, GetResult(miopenTensorMhaM)); + EXPECT_LT(M_error, error_threshold); + + double ZInv_error = miopen::rms_range(zInvDesc_ref, GetResult(miopenTensorMhaZInv)); + EXPECT_LT(ZInv_error, error_threshold); + + double O_error = miopen::rms_range(oDesc_ref, GetResult(miopenTensorMhaO)); + EXPECT_LT(O_error, error_threshold); + } +}; + +INSTANTIATE_TEST_SUITE_P(Fwd_Mha_Smoke, Test_Fwd_Mha, testing::ValuesIn(GetSmokeTestCases())); + +INSTANTIATE_TEST_SUITE_P(Fwd_Mha_Full, Test_Fwd_Mha, testing::ValuesIn(GetFullTestCases())); + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Test_Fwd_Mha); diff --git a/test/gtest/graphapi_convolution.cpp b/test/gtest/graphapi_convolution.cpp new file mode 100644 index 0000000000..f239adc14f --- /dev/null +++ b/test/gtest/graphapi_convolution.cpp @@ -0,0 +1,2108 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 +#include +#include +#include +#include + +#include +#include + +using GraphApiConvolutionTuple = std::tuple, + std::vector, + std::vector, + std::vector>; + +class GraphApiConvolution : public testing::TestWithParam +{ +protected: + void SetUp() override + { + std::tie(attrsValid, + compType, + mode, + spatialDims, + dilations, + filterStrides, + prePaddings, + postPaddings) = GetParam(); + } + miopenDataType_t compType; + miopenConvolutionMode_t mode; + int64_t spatialDims; + std::vector dilations; + std::vector filterStrides; + std::vector prePaddings; + std::vector postPaddings; + bool attrsValid; +}; + +TEST_P(GraphApiConvolution, BuilderValidateAttributes) +{ + bool thrown = false; + try + { + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + } + catch(...) + { + thrown = true; + } + EXPECT_NE(thrown, attrsValid) << "R-value builder failure"; + + thrown = false; + try + { + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + } + catch(...) + { + thrown = true; + } + EXPECT_NE(thrown, attrsValid) << "L-value builder failure"; +} + +TEST_P(GraphApiConvolution, RVBuilderMissingSetter) +{ + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setCompType() call"; + + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setMode() call"; + + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setMode(mode) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setSpatialDims() call"; + + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setDilations() call"; + + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setFilterStrides() call"; + + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setPrePaddings() call"; + + EXPECT_ANY_THROW({ + auto conv = miopen::graphapi::ConvolutionBuilder() + .setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setPostPaddings() call"; +} + +TEST_P(GraphApiConvolution, LVBuilderMissingSetter) +{ + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setCompType() call"; + + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setMode() call"; + + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setMode(mode) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setSpatialDims() call"; + + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setDilations() call"; + + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setPrePaddings(prePaddings) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setFilterStrides() call"; + + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPostPaddings(postPaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setPrePaddings() call"; + + EXPECT_ANY_THROW({ + miopen::graphapi::ConvolutionBuilder builder; + auto conv = builder.setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(dilations) + .setFilterStrides(filterStrides) + .setPrePaddings(prePaddings) + .build(); + }) << "Builder validated attributes despite missing " + "graphapi::ConvolutionBuilder::setPostPaddings() call"; +} + +TEST_P(GraphApiConvolution, BuilderCopyValues) +{ + auto srcDilations = dilations; + auto srcFilterStrides = filterStrides; + auto srcPrePaddings = prePaddings; + auto srcPostPaddings = postPaddings; + + auto srcDilationsAddress = srcDilations.data(); + auto srcFilterStridesAddress = srcFilterStrides.data(); + auto srcPrePaddingsAddress = srcPrePaddings.data(); + auto srcPostPaddingsAddress = srcPostPaddings.data(); + + bool thrown = false; + miopen::graphapi::Convolution conv; + try + { + miopen::graphapi::ConvolutionBuilder builder; + conv = builder.setCompType(compType) + .setMode(mode) + .setSpatialDims(spatialDims) + .setDilations(srcDilations) + .setFilterStrides(srcFilterStrides) + .setPrePaddings(srcPrePaddings) + .setPostPaddings(srcPostPaddings) + .build(); + } + catch(...) + { + thrown = true; + } + EXPECT_NE(thrown, attrsValid) << "graphapi::ConvolutionBuilder failure"; + + if(!attrsValid) + return; + + EXPECT_EQ(conv.getCompType(), compType) + << "graphapi::ConvolutionBuilder::setCompType didn't set parameter correctly"; + EXPECT_EQ(conv.getMode(), mode) + << "graphapi::ConvolutionBuilder::setMode didn't set parameter correctly"; + EXPECT_EQ(conv.getSpatialDims(), spatialDims) + << "graphapi::ConvolutionBuilder::setSpatialDims didn't set parameter correctly"; + EXPECT_THAT(conv.getDilations(), testing::ContainerEq(dilations)) + << "graphapi::ConvolutionBuilder::setDilations didn't set parameter correctly"; + EXPECT_THAT(conv.getFilterStrides(), testing::ContainerEq(filterStrides)) + << "graphapi::ConvolutionBuilder::setFilterStrides didn't set parameter correctly"; + EXPECT_THAT(conv.getPrePaddings(), testing::ContainerEq(prePaddings)) + << "graphapi::ConvolutionBuilder::setPrePaddings didn't set parameter correctly"; + EXPECT_THAT(conv.getPostPaddings(), testing::ContainerEq(postPaddings)) + << "graphapi::ConvolutionBuilder::setPostPaddings didn't set parameter correctly"; + + EXPECT_NE(conv.getDilations().data(), srcDilationsAddress) + << "graphapi::ConvolutionBuilder::setDilations unexpectedly moved the parameter"; + EXPECT_NE(conv.getFilterStrides().data(), srcFilterStridesAddress) + << "graphapi::ConvolutionBuilder::setFilterStrides unexpectedly moved the parameter"; + EXPECT_NE(conv.getPrePaddings().data(), srcPrePaddingsAddress) + << "graphapi::ConvolutionBuilder::setPrePaddings unexpectedly moved the parameter"; + EXPECT_NE(conv.getPostPaddings().data(), srcPostPaddingsAddress) + << "graphapi::ConvolutionBuilder::setPostPaddings unexpectedly moved the parameter"; +} + +TEST_P(GraphApiConvolution, BuilderMoveValues) +{ + auto srcDilations = dilations; + auto srcFilterStrides = filterStrides; + auto srcPrePaddings = prePaddings; + auto srcPostPaddings = postPaddings; + + auto srcDilationsAddress = srcDilations.data(); + auto srcFilterStridesAddress = srcFilterStrides.data(); + auto srcPrePaddingsAddress = srcPrePaddings.data(); + auto srcPostPaddingsAddress = srcPostPaddings.data(); + + bool thrown = false; + miopen::graphapi::Convolution conv; + try + { + miopen::graphapi::ConvolutionBuilder builder; + builder.setCompType(compType); + builder.setMode(mode); + builder.setSpatialDims(spatialDims); + builder.setDilations(std::move(srcDilations)); + builder.setFilterStrides(std::move(srcFilterStrides)); + builder.setPrePaddings(std::move(srcPrePaddings)); + builder.setPostPaddings(std::move(srcPostPaddings)); + conv = std::move(builder).build(); + } + catch(...) + { + thrown = true; + } + EXPECT_NE(thrown, attrsValid) << "graphapi::ConvolutionBuilder failure"; + + if(!attrsValid) + return; + + EXPECT_EQ(conv.getCompType(), compType) + << "graphapi::ConvolutionBuilder::setCompType didn't set the parameter correctly"; + EXPECT_EQ(conv.getMode(), mode) + << "graphapi::ConvolutionBuilder::setMode didn't set the parameter correctly"; + EXPECT_EQ(conv.getSpatialDims(), spatialDims) + << "graphapi::ConvolutionBuilder::setSpatialDims didn't set the parameter correctly"; + EXPECT_THAT(conv.getDilations(), testing::ContainerEq(dilations)) + << "graphapi::ConvolutionBuilder::setDilations didn't set the parameter correctly"; + EXPECT_THAT(conv.getFilterStrides(), testing::ContainerEq(filterStrides)) + << "graphapi::ConvolutionBuilder::setFilterStrides didn't set parameter correctly"; + EXPECT_THAT(conv.getPrePaddings(), testing::ContainerEq(prePaddings)) + << "graphapi::ConvolutionBuilder::setPrePaddings didn't set parameter correctly"; + EXPECT_THAT(conv.getPostPaddings(), testing::ContainerEq(postPaddings)) + << "graphapi::ConvolutionBuilder::setPostPaddings didn't set parameter correctly"; + + EXPECT_EQ(conv.getDilations().data(), srcDilationsAddress) + << "graphapi::ConvolutionBuilder::setDilations didn't move the parameter"; + EXPECT_EQ(conv.getFilterStrides().data(), srcFilterStridesAddress) + << "graphapi::ConvolutionBuilder::setFilterStrides didn't move the parameter"; + EXPECT_EQ(conv.getPrePaddings().data(), srcPrePaddingsAddress) + << "graphapi::ConvolutionBuilder::setPrePaddings didn't move the parameter"; + EXPECT_EQ(conv.getPostPaddings().data(), srcPostPaddingsAddress) + << "graphapi::ConvolutionBuilder::setPostPaddings didn't move the parameter"; +} + +TEST_P(GraphApiConvolution, CFunctions) +{ + // clang-format off + // Create Desctiptor + miopenBackendDescriptor_t descrConv; + miopenStatus_t status = miopenBackendCreateDescriptor(MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR, &descrConv); + ASSERT_EQ(status, miopenStatusSuccess) << "MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR wasn't created"; + ASSERT_NE(descrConv, nullptr) << "A null MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR was created"; + + // Finalize before setting attributes + status = miopenBackendFinalize(descrConv); + if(status == miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descrConv); + FAIL() << "MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR was finalized without setting attributes"; + } + + // Set compType + bool allParamsSet = true; + char buffer[64] = {0}; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_BOOLEAN, 1, &compType); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 2, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &compType); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Set mode + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_BOOLEAN, 1, &mode); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 2, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 1, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 1, &mode); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Set spatialDims + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_BOOLEAN, 1, &spatialDims); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 2, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 1, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 1, &spatialDims); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Set dilations + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_BOOLEAN, dilations.size(), dilations.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, 0, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, dilations.size(), nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, dilations.size(), dilations.data()); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Set filterStrides + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_BOOLEAN, filterStrides.size(), filterStrides.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, 0, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, filterStrides.size(), nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, filterStrides.size(), filterStrides.data()); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Set prePaddings + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_BOOLEAN, prePaddings.size(), prePaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, 0, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, prePaddings.size(), nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, prePaddings.size(), prePaddings.data()); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Set postPaddings + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_BOOLEAN, postPaddings.size(), postPaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was set with invalid type"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, 0, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was set with invalid element count"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, postPaddings.size(), nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was set with null array of elements"; + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, postPaddings.size(), postPaddings.data()); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS wasn't set"; + allParamsSet = allParamsSet && (status == miopenStatusSuccess); + + // Get attibute before finalizing + miopenDataType_t gotCompType = miopenHalf; + int64_t elementCount = 0; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &elementCount, &gotCompType); + if(status == miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descrConv); + FAIL() << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was retrieved before finalize()"; + } + + if(!allParamsSet && attrsValid) + { + miopenBackendDestroyDescriptor(descrConv); + FAIL() << "Not all attributes of MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR were set"; + } + + // Finalize + status = miopenBackendFinalize(descrConv); + if(attrsValid && status != miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descrConv); + FAIL() << "MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR wasn't finalized"; + } + else if(!attrsValid) + { + miopenBackendDestroyDescriptor(descrConv); + ASSERT_NE(status, miopenStatusSuccess) << "MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR was finalized on invalid attributes"; + return; // no need to continue with non-finalized descriptor + } + + // Set Attributes after finalizing + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &compType); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was set after finalize()"; + + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 1, &mode); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was set after finalize()"; + + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 1, &spatialDims); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was set after finalize()"; + + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, dilations.size(), dilations.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was set after finalize()"; + + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, filterStrides.size(), filterStrides.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was set after finalize()"; + + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, prePaddings.size(), prePaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was set after finalize()"; + + status = miopenBackendSetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, postPaddings.size(), postPaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was set after finalize()"; + + // Get compType + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_BOOLEAN, 1, &elementCount, &gotCompType); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 2, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, nullptr, &gotCompType); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &elementCount, &gotCompType); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_EQ(gotCompType, compType) << "MIOPEN_ATTR_CONVOLUTION_COMP_TYPE set and retrieved values differ"; + + // Get mode + miopenConvolutionMode_t gotMode; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_BOOLEAN, 1, &elementCount, &gotMode); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 2, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 1, nullptr, &gotMode); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 1, &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_CONV_MODE, MIOPEN_TYPE_CONVOLUTION_MODE, 1, &elementCount, &gotMode); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_EQ(gotMode, mode) << "MIOPEN_ATTR_CONVOLUTION_CONV_MODE set and retrieved values differ"; + + // Get spatialDims + int64_t gotSpatialDims = 0; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_BOOLEAN, 1, &elementCount, &gotSpatialDims); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 2, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 1, nullptr, &gotSpatialDims); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 1, &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS, MIOPEN_TYPE_INT64, 1, &elementCount, &gotSpatialDims); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_EQ(gotSpatialDims, spatialDims) << "MIOPEN_ATTR_CONVOLUTION_SPATIAL_DIMS set and retrieved values differ"; + + // Get dilations + std::vector gotDilations(dilations.size()); + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_BOOLEAN, gotDilations.size(), &elementCount, gotDilations.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, 0, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, gotDilations.size(), nullptr, gotDilations.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, gotDilations.size(), &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_DILATIONS, MIOPEN_TYPE_INT64, gotDilations.size(), &elementCount, gotDilations.data()); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_THAT(gotDilations, testing::ContainerEq(dilations)) << "MIOPEN_ATTR_CONVOLUTION_DILATIONS set and retrieved values differ"; + + // Get filterStrides + std::vector gotFilterStrides(filterStrides.size()); + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_BOOLEAN, gotFilterStrides.size(), &elementCount, gotFilterStrides.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, 0, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, gotFilterStrides.size(), nullptr, gotFilterStrides.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, gotFilterStrides.size(), &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES, MIOPEN_TYPE_INT64, gotFilterStrides.size(), &elementCount, gotFilterStrides.data()); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_THAT(gotFilterStrides, testing::ContainerEq(filterStrides)) << "MIOPEN_ATTR_CONVOLUTION_FILTER_STRIDES set and retrieved values differ"; + + // Get prePaddings + std::vector gotPrePaddings(prePaddings.size()); + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_BOOLEAN, gotPrePaddings.size(), &elementCount, gotPrePaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, 0, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, gotPrePaddings.size(), nullptr, gotPrePaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, gotPrePaddings.size(), &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS, MIOPEN_TYPE_INT64, gotPrePaddings.size(), &elementCount, gotPrePaddings.data()); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_THAT(gotPrePaddings, testing::ContainerEq(prePaddings)) << "MIOPEN_ATTR_CONVOLUTION_PRE_PADDINGS set and retrieved values differ"; + + // Get postPaddings + std::vector gotPostPaddings(postPaddings.size()); + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_BOOLEAN, gotPostPaddings.size(), &elementCount, gotPostPaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was retrieved with invalid type"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, 0, &elementCount, buffer); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, gotPostPaddings.size(), nullptr, gotPostPaddings.data()); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was retrieved with null element count"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, gotPostPaddings.size(), &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descrConv, MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS, MIOPEN_TYPE_INT64, gotPostPaddings.size(), &elementCount, gotPostPaddings.data()); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_THAT(gotPostPaddings, testing::ContainerEq(postPaddings)) << "MIOPEN_ATTR_CONVOLUTION_POST_PADDINGS set and retrieved values differ"; + + // Destroy description + status = miopenBackendDestroyDescriptor(descrConv); + EXPECT_EQ(status, miopenStatusSuccess) << "MIOPEN_BACKEND_CONVOLUTION_DESCRIPTOR destroyed with non-success status"; + // clang-format on +} + +INSTANTIATE_TEST_SUITE_P( + GraphApiConvolution, + GraphApiConvolution, + testing::Values( + GraphApiConvolutionTuple{ + true, miopenInt8, miopenConvolution, 2, {5, 6}, {20, 21}, {3, 4}, {1, 2}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 3, {1, 1}, {1, 1}, {0, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1, 1}, {1, 1}, {0, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1}, {1, 1, 1}, {0, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1}, {1, 1}, {0, 0, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1}, {1, 1}, {0, 0}, {0, 0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 0}, {1, 1}, {0, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1}, {1, 0}, {0, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1}, {1, 1}, {-1, 0}, {0, 0}}, + GraphApiConvolutionTuple{ + false, miopenInt8, miopenConvolution, 2, {1, 1}, {1, 1}, {0, 0}, {0, -1}})); + +template +class GraphApiOperationConvolutionBuilder : public testing::Test +{ +protected: + using TestCase = std::tuple; + + void SetUp() override + { + testCases = {TestCase{true, &this->convolution, &this->x, &this->y, &this->w, ""}, + TestCase{false, nullptr, &this->x, &this->y, &this->w, "convolution"}, + TestCase{false, &this->convolution, nullptr, &this->y, &this->w, "X tensor"}, + TestCase{false, &this->convolution, &this->x, nullptr, &this->w, "Y tensor"}, + TestCase{false, &this->convolution, &this->x, &this->y, nullptr, "W tensor"}}; + } + + miopen::graphapi::Convolution convolution; + miopen::graphapi::Tensor x; + miopen::graphapi::Tensor y; + miopen::graphapi::Tensor w; + double dAlpha = 1.0; + double dBeta = 0.0; + float fAlpha = 1.0f; + float fBeta = 0.0f; + + std::array testCases; +}; + +using GraphApiOperationConvolutionBuilderClasses = + testing::Types; + +TYPED_TEST_SUITE(GraphApiOperationConvolutionBuilder, GraphApiOperationConvolutionBuilderClasses); + +TYPED_TEST(GraphApiOperationConvolutionBuilder, ValidateAttributes) +{ + for(auto [attrsValid, convolution, x, y, w, message] : this->testCases) + { + if(attrsValid) + { + EXPECT_NO_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setX(x) + .setY(y) + .setW(w) + .setAlpha(this->dAlpha) + .setBeta(this->dBeta) + .build(); + }) << "Builder didn't validate correct attributes"; + } + else + { + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setX(x) + .setY(y) + .setW(w) + .setAlpha(this->dAlpha) + .setBeta(this->dBeta) + .build(); + }) << "Builder validated incorrect " + << message; + } + } +} + +TYPED_TEST(GraphApiOperationConvolutionBuilder, MissingSetter) +{ + for(auto [attrsValid, convolution, x, y, w, message] : this->testCases) + { + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setX(&this->x) + .setY(&this->y) + .setW(&this->w) + .setAlpha(this->dAlpha) + .setBeta(this->dBeta) + .build(); + }) << "Builder validated attributes despite missing" + "setConvolution() call"; + + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setY(y) + .setW(w) + .setAlpha(this->dAlpha) + .setBeta(this->dBeta) + .build(); + }) << "Builder validated attributes despite missing" + "setX() call"; + + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setX(x) + .setW(w) + .setAlpha(this->dAlpha) + .setBeta(this->dBeta) + .build(); + }) << "Builder validated attributes despite missing" + "setY() call"; + + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setX(x) + .setY(y) + .setAlpha(this->dAlpha) + .setBeta(this->dBeta) + .build(); + }) << "Builder validated attributes despite missing" + "setW() call"; + + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setX(x) + .setY(y) + .setW(w) + .setBeta(this->dBeta) + .build(); + }) << "Builder validated attributes despite missing" + "setAlpha() call"; + + EXPECT_ANY_THROW({ + auto op = TypeParam() + .setConvolution(convolution) + .setX(x) + .setY(y) + .setW(w) + .setAlpha(this->dAlpha) + .build(); + }) << "Builder validated attributes despite missing" + "setBeta() call"; + } +} + +namespace { + +struct GTestDescAttr +{ + struct TestCase + { + const char* textName; + miopenBackendAttributeName_t name; + miopenBackendAttributeType_t type; + int64_t count; + void* data; + + miopenBackendAttributeType_t invalidType; + void* invalidTypeData; + + int64_t invalidCount; + void* invalidCountData; + + void* readBuffer; + }; + + GTestDescAttr() = default; + + TestCase testCase() const { return mTestCase; } + virtual bool testReadBuffer() = 0; + + virtual ~GTestDescAttr() = default; + +protected: + TestCase mTestCase; +}; + +template +struct GTestDescAttrValues : GTestDescAttr +{ + GTestDescAttrValues(const char* textName, + miopenBackendAttributeName_t name, + miopenBackendAttributeType_t type, + miopenBackendAttributeType_t invalidType, + int64_t invalidCount, + std::initializer_list values) + : mValues(values), + mInvalidTypeValues(std::max(static_cast(1), values.size())), + mInvalidCountValues(std::max(static_cast(1), invalidCount), + values.size() > 0 ? *values.begin() : ValueType{}), + mReadValues(values.size()) + { + mTestCase.textName = textName; + mTestCase.name = name; + mTestCase.type = type; + mTestCase.count = mValues.size(); + mTestCase.data = mValues.data(); + + mTestCase.invalidType = invalidType; + mTestCase.invalidTypeData = mInvalidTypeValues.data(); + + mTestCase.invalidCount = invalidCount; + mTestCase.invalidCountData = mInvalidCountValues.data(); + + mTestCase.readBuffer = mReadValues.data(); + } + virtual bool testReadBuffer() override + { + return std::equal(mValues.begin(), mValues.end(), mReadValues.begin()); + } + +private: + std::vector mValues; + std::vector mInvalidTypeValues; + std::vector mInvalidCountValues; + std::vector mReadValues; +}; + +struct GTestDescriptor +{ + const char* textName; + miopenBackendDescriptorType_t type; + bool attrsValid; + std::vector> attributes; +}; + +using GTestAttrAlphaDouble = GTestDescAttrValues; +using GTestAttrAlphaFloat = GTestDescAttrValues; + +struct GTestAttrConv : GTestDescAttrValues +{ + GTestAttrConv(const char* textName, + miopenBackendAttributeName_t name, + bool finalized = true, + bool nullValue = false) + : GTestDescAttrValues( + textName, + name, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{nullValue ? nullptr : &mConv}), + mConv(finalized) + { + } + +private: + struct Descr : miopen::graphapi::BackendConvolutionDescriptor + { + Descr(bool finalized) { mFinalized = finalized; } + }; + Descr mConv; +}; + +struct GTestAttrTensor : GTestDescAttrValues +{ + GTestAttrTensor(const char* textName, + miopenBackendAttributeName_t name, + bool finalized = true, + bool nullValue = false) + : GTestDescAttrValues( + textName, + name, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{nullValue ? nullptr : &mTens}), + mTens(finalized) + { + } + +private: + struct Descr : miopen::graphapi::BackendTensorDescriptor + { + Descr(bool finalized) { mFinalized = finalized; } + }; + Descr mTens; +}; + +} // namespace + +class GraphApiOperationConvolution : public testing::TestWithParam +{ +}; + +TEST_P(GraphApiOperationConvolution, CFuntions) +{ + auto [descrTextName, descrType, attrsValid, attributes] = GetParam(); + + // Create Desctiptor + miopenBackendDescriptor_t descr; + // clang-format off + miopenStatus_t status = miopenBackendCreateDescriptor(descrType, &descr); + ASSERT_EQ(status, miopenStatusSuccess) << descrTextName << " wasn't created"; + ASSERT_NE(descr, nullptr) << "A null " << descrTextName << " was created"; + // clang-format on + + // Finalize before setting attributes + status = miopenBackendFinalize(descr); + if(status == miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << descrTextName << " was finalized without setting attributes"; + } + + // Set attributes (should succeed) + bool anyAttributeFailed = false; + for(auto& attrPtr : attributes) + { + auto [textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->testCase(); + + // clang-format off + status = miopenBackendSetAttribute(descr, name, invalidType, count, invalidTypeData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set with invalid type"; + + status = miopenBackendSetAttribute(descr, name, type, invalidCount, invalidCountData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set with invalid element count"; + + status = miopenBackendSetAttribute(descr, name, type, count, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set with null array of elements"; + + status = miopenBackendSetAttribute(descr, name, type, count, data); + if(attrsValid) // implementation may postpone validating values to finalize() + EXPECT_EQ(status, miopenStatusSuccess) << textName << " wasn't set"; + // clang-format on + + anyAttributeFailed = anyAttributeFailed || (status != miopenStatusSuccess); + } + + // Get attibute before finalizing (not a one should succeed) + bool anyAttributeGot = false; + for(auto& attrPtr : attributes) + { + auto [textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->testCase(); + + int64_t elementCount = 0; + + status = miopenBackendGetAttribute(descr, name, type, count, &elementCount, readBuffer); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved before finalize()"; + + anyAttributeGot = anyAttributeGot || (status == miopenStatusSuccess); + } + + // Stop further execution if needed + if(anyAttributeGot) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << "Some attributes of " << descrTextName << " were retrieved before finalize()"; + } + if(anyAttributeFailed && attrsValid) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << "Not all attributes of " << descrTextName << " were set"; + } + + // Finalize + status = miopenBackendFinalize(descr); + + // Stop further execution if finalize() acted incorrectly + if(attrsValid && status != miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << descrTextName << " wasn't finalized"; + } + else if(!attrsValid) + { + miopenBackendDestroyDescriptor(descr); + ASSERT_NE(status, miopenStatusSuccess) + << descrTextName << " was finalized on invalid attributes"; + + // No need to proceed with invalid attributes + return; + } + + // Set attributes after finalizing (not a one should succeed) + bool anyAttributeSet = false; + for(auto& attrPtr : attributes) + { + auto [textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->testCase(); + + status = miopenBackendSetAttribute(descr, name, type, count, data); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set after finalize()"; + + anyAttributeSet = anyAttributeSet || (status == miopenStatusSuccess); + } + + // Stop if an attribute was set + if(anyAttributeSet) + { + miopenBackendDestroyDescriptor(descr); + ASSERT_NE(status, miopenStatusSuccess) + << "An attribute of " << descrTextName << " was set after finalize()"; + } + + // Get attributes + for(auto& attrPtr : attributes) + { + auto [textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->testCase(); + + int64_t elementCount = 0; + // clang-format off + status = miopenBackendGetAttribute(descr, name, invalidType, count, &elementCount, invalidTypeData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with invalid type"; + status = miopenBackendGetAttribute(descr, name, type, invalidCount, &elementCount, invalidCountData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with invalid element count"; + status = miopenBackendGetAttribute(descr, name, type, count, nullptr, readBuffer); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with null element count"; + status = miopenBackendGetAttribute(descr, name, type, count, &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with null array of elements"; + status = miopenBackendGetAttribute(descr, name, type, count, &elementCount, readBuffer); + EXPECT_EQ(status, miopenStatusSuccess) << textName << " wasn't retrieved"; + if(status == miopenStatusSuccess) + EXPECT_TRUE(attrPtr->testReadBuffer()) << textName << " set and retrieved values differ"; + // clang-format on + } +} + +// TODO: Use testing::Combine to make +// this list concise +INSTANTIATE_TEST_SUITE_P( + GraphApiOperationConvolution, + GraphApiOperationConvolution, + testing::Values( + + // Forward valid + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + true, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + true, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Forward non-finalized attr + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Forward null attr + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_FORWARD_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_X, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_Y, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_W, + true, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_FORWARD_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Bwd data valid + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + true, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + true, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Bwd data non-finalized attr + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Bwd data null attr + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_DATA_DESCRIPTOR, + false, + {std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DX, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_DY, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_W, + true, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_DATA_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Bwd filter valid + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + true, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + true, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_FLOAT, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Bwd non-finalized attr + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + + // Bwd null attr + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true, + true), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true, + false), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}}, + GTestDescriptor{ + "MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_CONVOLUTION_BACKWARD_FILTER_DESCRIPTOR, + false, + {std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_CONV_DESC, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_X, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DY, + true, + false), + std::make_shared("MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_DW, + true, + true), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_ALPHA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.9}), + std::make_shared( + "MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA", + MIOPEN_ATTR_OPERATION_CONVOLUTION_BWD_FILTER_BETA, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 0, + std::initializer_list{0.1})}} + + )); diff --git a/test/gtest/graphapi_gtest_common.hpp b/test/gtest/graphapi_gtest_common.hpp new file mode 100644 index 0000000000..120e8c0649 --- /dev/null +++ b/test/gtest/graphapi_gtest_common.hpp @@ -0,0 +1,448 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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. + * + *******************************************************************************/ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace miopen { + +namespace graphapi { + +template +struct ValidatedVector +{ + bool valid; + std::vector values; + + friend void PrintTo(const ValidatedVector& v, std::ostream* os) + { + *os << '{'; + auto begin = v.values.cbegin(); + auto end = v.values.cend(); + if(begin != end) + *os << *begin++; + while(begin != end) + *os << ' ' << *begin++; + *os << '}'; + } +}; + +template +struct ValidatedValue +{ + bool valid; + T value; + + friend void PrintTo(const ValidatedValue& v, std::ostream* os) { *os << v.value; } +}; + +class GTestDescriptorAttribute +{ +public: + struct TestCase + { + bool isCorrect; + + const char* textName; + miopenBackendAttributeName_t name; + miopenBackendAttributeType_t type; + int64_t count; + void* data; + + miopenBackendAttributeType_t invalidType; + void* invalidTypeData; + + int64_t invalidCount; + void* invalidCountData; + + void* readBuffer; + }; + +protected: + TestCase mTestCase; + +public: + GTestDescriptorAttribute() = default; + GTestDescriptorAttribute(const TestCase& testCase) : mTestCase(testCase) {} + + TestCase getTestCase() const { return mTestCase; } + + virtual testing::AssertionResult isSetAndGotEqual() = 0; + + virtual ~GTestDescriptorAttribute() = default; +}; + +template +class GTestDescriptorVectorAttribute : public GTestDescriptorAttribute +{ +protected: + // For several values we use a vector. + // For a single value attribute here would have been + // a single member of type ValueType. + std::vector mValues; + // But the rest of the fields should be nevertheless + // vectors, so we'll treat a single value attribute + // as special case of vector attribute + std::vector mInvalidTypeValues; + std::vector mInvalidCountValues; + std::vector mReadValues; + +public: + GTestDescriptorVectorAttribute(bool isCorrect, + const char* textName, + miopenBackendAttributeName_t name, + miopenBackendAttributeType_t type, + miopenBackendAttributeType_t invalidType, + int64_t invalidCount, + std::initializer_list values) + : GTestDescriptorVectorAttribute(isCorrect, + textName, + name, + type, + invalidType, + invalidCount, + std::vector(values)) + { + } + + GTestDescriptorVectorAttribute() = default; + GTestDescriptorVectorAttribute(bool isCorrect, + const char* textName, + miopenBackendAttributeName_t name, + miopenBackendAttributeType_t type, + miopenBackendAttributeType_t invalidType, + int64_t invalidCount, + const std::vector& values) + : mValues(values), + mInvalidTypeValues(std::max(static_cast(1), values.size())), + mInvalidCountValues(std::max(static_cast(1), invalidCount), + values.empty() ? ValueType() : *values.begin()), + mReadValues(std::max(static_cast(1), values.size())) + { + mTestCase.isCorrect = isCorrect; + + mTestCase.textName = textName; + mTestCase.name = name; + mTestCase.type = type; + mTestCase.count = mValues.size(); + mTestCase.data = mValues.empty() ? mReadValues.data() : mValues.data(); + + mTestCase.invalidType = invalidType; + mTestCase.invalidTypeData = mInvalidTypeValues.data(); + + mTestCase.invalidCount = invalidCount; + mTestCase.invalidCountData = mInvalidCountValues.data(); + + mTestCase.readBuffer = mReadValues.data(); + } + + virtual testing::AssertionResult isSetAndGotEqual() override + { + if(std::equal(mValues.begin(), mValues.end(), mReadValues.begin())) + { + return testing::AssertionSuccess(); + } + else + { + return testing::AssertionFailure(); + } + } +}; + +template +class GTestDescriptorSingleValueAttribute + : public GTestDescriptorVectorAttribute +{ +public: + GTestDescriptorSingleValueAttribute() = default; + GTestDescriptorSingleValueAttribute(bool isCorrect, + const char* textName, + miopenBackendAttributeName_t name, + miopenBackendAttributeType_t type, + miopenBackendAttributeType_t invalidType, + int64_t invalidCount, + ValueType value) + : GTestDescriptorVectorAttribute( + isCorrect, textName, name, type, invalidType, invalidCount, {value}) + { + } + + virtual testing::AssertionResult isSetAndGotEqual() override + { + assert(this->mValues.size() == this->mReadValues.size()); + if(this->mValues[0] == this->mReadValues[0]) + { + return testing::AssertionSuccess(); + } + else + { + return testing::AssertionFailure() + << "is " << this->mReadValues[0] << " but should be " << this->mValues[0]; + } + } +}; + +template > +struct GTestDescriptor +{ + const char* textName = ""; + miopenBackendDescriptorType_t type = miopenBackendDescriptorType_t(0); + bool attrsValid = false; + std::vector attributes; +}; + +template > +struct GTestGraphApiExecute +{ + GTestDescriptor descriptor; + + void operator()() + { + auto [descrTextName, descrType, attrsValid, attributes] = descriptor; + + // Create Desctiptor + miopenBackendDescriptor_t descr; + // clang-format off + miopenStatus_t status = miopenBackendCreateDescriptor(descrType, &descr); + ASSERT_EQ(status, miopenStatusSuccess) << descrTextName << " wasn't created"; + ASSERT_NE(descr, nullptr) << "A null " << descrTextName << " was created"; + // clang-format on + + // Finalize before setting attributes + status = miopenBackendFinalize(descr); + if(status == miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << descrTextName << " was finalized without setting attributes"; + } + + // Set attributes (should succeed) + for(auto& attrPtr : attributes) + { + auto [isCorrect, + textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->getTestCase(); + + // clang-format off + status = miopenBackendSetAttribute(descr, name, invalidType, count, invalidTypeData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set with invalid type"; + + status = miopenBackendSetAttribute(descr, name, type, invalidCount, invalidCountData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set with invalid element count"; + + status = miopenBackendSetAttribute(descr, name, type, count, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set with null array of elements"; + + status = miopenBackendSetAttribute(descr, name, type, count, data); + if(isCorrect) EXPECT_EQ(status, miopenStatusSuccess) << textName << " wasn't set"; + else EXPECT_NE(status, miopenStatusSuccess) << textName << " was set to invalid value"; + // clang-format on + } + + // Get attibute before finalizing (not a one should succeed) + bool anyAttributeGot = false; + for(auto& attrPtr : attributes) + { + auto [isCorrect, + textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->getTestCase(); + + int64_t elementCount = 0; + + status = miopenBackendGetAttribute(descr, name, type, count, &elementCount, readBuffer); + EXPECT_NE(status, miopenStatusSuccess) + << textName << " was retrieved before finalize()"; + + anyAttributeGot = anyAttributeGot || (status == miopenStatusSuccess); + } + + // Stop further execution if needed + if(anyAttributeGot) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << "Some attributes of " << descrTextName << " were retrieved before finalize()"; + } + + // Finalize + status = miopenBackendFinalize(descr); + + // Stop further execution if finalize() acted incorrectly + if(attrsValid && status != miopenStatusSuccess) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << descrTextName << " wasn't finalized"; + } + else if(!attrsValid) + { + miopenBackendDestroyDescriptor(descr); + ASSERT_NE(status, miopenStatusSuccess) + << descrTextName << " was finalized on invalid attributes"; + + // No need to proceed with invalid attributes + return; + } + + // Set attributes after finalizing (not a one should succeed) + bool anyAttributeSet = false; + for(auto& attrPtr : attributes) + { + auto [isCorrect, + textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->getTestCase(); + + status = miopenBackendSetAttribute(descr, name, type, count, data); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was set after finalize()"; + + anyAttributeSet = anyAttributeSet || (status == miopenStatusSuccess); + } + + // Stop if an attribute was set + if(anyAttributeSet) + { + miopenBackendDestroyDescriptor(descr); + FAIL() << "An attribute of " << descrTextName << " was set after finalize()"; + } + + // Get attributes + for(auto& attrPtr : attributes) + { + auto [isCorrect, + textName, + name, + type, + count, + data, + invalidType, + invalidTypeData, + invalidCount, + invalidCountData, + readBuffer] = attrPtr->getTestCase(); + + int64_t elementCount = 0; + // clang-format off + status = miopenBackendGetAttribute(descr, name, invalidType, count, &elementCount, invalidTypeData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with invalid type"; + + status = miopenBackendGetAttribute(descr, name, type, invalidCount, &elementCount, invalidCountData); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with invalid element count"; + + status = miopenBackendGetAttribute(descr, name, type, count, nullptr, readBuffer); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with null element count"; + + status = miopenBackendGetAttribute(descr, name, type, count, &elementCount, nullptr); + EXPECT_NE(status, miopenStatusSuccess) << textName << " was retrieved with null array of elements"; + + if(isCorrect) + { + status = miopenBackendGetAttribute(descr, name, type, count, &elementCount, readBuffer); + + EXPECT_EQ(status, miopenStatusSuccess) << textName << " wasn't retrieved"; + EXPECT_EQ(count, elementCount) << textName << " set and retrieved number of elements differ"; + + if(status == miopenStatusSuccess && count == elementCount) + { + EXPECT_TRUE(attrPtr->isSetAndGotEqual()) << textName << " set and retrieved values differ"; + } + } + // clang-format on + } + } +}; + +class GMockBackendTensorDescriptor : public BackendTensorDescriptor +{ +public: + GMockBackendTensorDescriptor& operator=(const Tensor& testCaseTensor) + { + auto dataType = testCaseTensor.getDataType(); + setAttribute(MIOPEN_ATTR_TENSOR_DATA_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &dataType); + + auto dims = testCaseTensor.getDimensions(); + setAttribute(MIOPEN_ATTR_TENSOR_DIMENSIONS, MIOPEN_TYPE_INT64, dims.size(), dims.data()); + + auto strides = testCaseTensor.getStrides(); + setAttribute(MIOPEN_ATTR_TENSOR_STRIDES, MIOPEN_TYPE_INT64, strides.size(), strides.data()); + + auto id = testCaseTensor.getId(); + setAttribute(MIOPEN_ATTR_TENSOR_UNIQUE_ID, MIOPEN_TYPE_INT64, 1, &id); + + auto isVirtual = testCaseTensor.isVirtual(); + setAttribute(MIOPEN_ATTR_TENSOR_IS_VIRTUAL, MIOPEN_TYPE_BOOLEAN, 1, &isVirtual); + + finalize(); + + return *this; + } + + GMockBackendTensorDescriptor& operator=(const ValidatedValue& validatedTestCaseTensor) + { + if(validatedTestCaseTensor.valid) + { + return *this = *validatedTestCaseTensor.value; + } + else + { + return *this; + } + } +}; + +} // namespace graphapi + +} // namespace miopen diff --git a/test/gtest/graphapi_matmul.cpp b/test/gtest/graphapi_matmul.cpp new file mode 100644 index 0000000000..2ff48bb239 --- /dev/null +++ b/test/gtest/graphapi_matmul.cpp @@ -0,0 +1,88 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "graphapi_gtest_common.hpp" + +TEST(GraphApiMatmulBuilder, Attributes) +{ + EXPECT_ANY_THROW({ miopen::graphapi::MatmulBuilder().build(); }) + << "Builder produced Matmul despite missing setComputeType call"; + EXPECT_NO_THROW({ miopen::graphapi::MatmulBuilder().setComputeType(miopenDouble).build(); }) + << "Builder failed to produce Matmul with valid attributes"; +} + +namespace { + +using miopen::graphapi::GTestDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +class ComputeType : public GTestDescriptorSingleValueAttribute +{ +public: + ComputeType() = default; + ComputeType(miopenDataType_t computeType) + : GTestDescriptorSingleValueAttribute( + true, + "MIOPEN_ATTR_MATMUL_COMP_TYPE", + MIOPEN_ATTR_MATMUL_COMP_TYPE, + MIOPEN_TYPE_DATA_TYPE, + MIOPEN_TYPE_CHAR, + 2, + computeType) + { + } +}; +} // namespace + +void PrintTo(const miopenDataType_t& v, std::ostream* os) { *os << "compute type: " << v; } + +class GraphApiMatMul : public testing::TestWithParam +{ +protected: + GTestGraphApiExecute execute; + + ComputeType computeType; + + void SetUp() override + { + auto compType = GetParam(); + computeType = compType; + execute.descriptor.attributes = {&computeType}; + execute.descriptor.attrsValid = true; + execute.descriptor.textName = "MIOPEN_BACKEND_MATMUL_DESCRIPTOR"; + execute.descriptor.type = MIOPEN_BACKEND_MATMUL_DESCRIPTOR; + } +}; + +TEST_P(GraphApiMatMul, CFuncions) { execute(); } + +INSTANTIATE_TEST_SUITE_P(CFuncionsTest, GraphApiMatMul, testing::Values(miopenFloat, miopenDouble)); diff --git a/test/gtest/graphapi_operation_matmul.cpp b/test/gtest/graphapi_operation_matmul.cpp new file mode 100644 index 0000000000..5595322766 --- /dev/null +++ b/test/gtest/graphapi_operation_matmul.cpp @@ -0,0 +1,473 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "graphapi_gtest_common.hpp" + +namespace { + +using miopen::graphapi::Matmul; +using miopen::graphapi::OperationMatmul; +using miopen::graphapi::OperationMatmulBuilder; +using miopen::graphapi::Tensor; + +using miopen::graphapi::ValidatedValue; + +using DescriptorTuple = std::tuple, + ValidatedValue, + ValidatedValue, + ValidatedValue, + int64_t, + ValidatedValue, + ValidatedValue, + ValidatedValue>; + +} // namespace + +class GraphApiOperationMatmulBuilder : public testing::TestWithParam +{ +protected: + bool mAttrsValid; + ValidatedValue mMatmul; + ValidatedValue mA; + ValidatedValue mB; + ValidatedValue mC; + int64_t mBatchCount; + ValidatedValue mGemmMOverride; + ValidatedValue mGemmNOverride; + ValidatedValue mGemmKOverride; + + void SetUp() override + { + std::tie(mAttrsValid, + mMatmul, + mA, + mB, + mC, + mBatchCount, + mGemmMOverride, + mGemmNOverride, + mGemmKOverride) = GetParam(); + } + + OperationMatmul build() + { + return OperationMatmulBuilder() + .setA(mA.value) + .setB(mB.value) + .setC(mC.value) + .setBatchCount(mBatchCount) + .setGemmMOverride(mGemmMOverride.value) + .setGemmNOverride(mGemmNOverride.value) + .setGemmKOverride(mGemmKOverride.value) + .setMatmulDescriptor(mMatmul.value) + .build(); + } +}; + +TEST_P(GraphApiOperationMatmulBuilder, ValidateAttributes) +{ + if(mAttrsValid) + { + EXPECT_NO_THROW({ build(); }) << "Builder failed on valid attributes"; + } + else + { + EXPECT_ANY_THROW({ build(); }) << "Builder failed to detect invalid attributes"; + if(!mMatmul.valid || !mA.valid || !mB.valid || !mC.valid || !mGemmMOverride.valid || + !mGemmNOverride.valid || !mGemmKOverride.valid) + { + EXPECT_ANY_THROW({ build(); }) << "Builder failed to detect invalid attributes"; + } + } + + if(mMatmul.valid) + { + EXPECT_NO_THROW({ OperationMatmulBuilder().setMatmulDescriptor(mMatmul.value); }) + << "OperationMatmulBuilder::setMatmul failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationMatmulBuilder().setMatmulDescriptor(mMatmul.value); }) + << "OperationMatmulBuilder::setMatmul failed with an invalid attribute"; + } + + if(mA.valid) + { + EXPECT_NO_THROW({ OperationMatmulBuilder().setA(mA.value); }) + << "OperationMatmulBuilder::setA failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationMatmulBuilder().setA(mA.value); }) + << "OperationMatmulBuilder::setA failed with an invalid attribute"; + } + + if(mB.valid) + { + EXPECT_NO_THROW({ OperationMatmulBuilder().setB(mB.value); }) + << "OperationMatmulBuilder::setB failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationMatmulBuilder().setB(mB.value); }) + << "OperationMatmulBuilder::setB failed with an invalid attribute"; + } + + if(mC.valid) + { + EXPECT_NO_THROW({ OperationMatmulBuilder().setC(mC.value); }) + << "OperationMatmulBuilder::setC failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationMatmulBuilder().setC(mC.value); }) + << "OperationMatmulBuilder::setC failed with an invalid attribute"; + } +} + +TEST_P(GraphApiOperationMatmulBuilder, MissingSetter) +{ + EXPECT_ANY_THROW({ + OperationMatmulBuilder().setA(mA.value).setB(mB.value).setC(mC.value).build(); + }) << "Builder failed to detect missing setMatmulDescriptor() call"; + EXPECT_ANY_THROW({ + OperationMatmulBuilder() + .setB(mB.value) + .setC(mC.value) + .setMatmulDescriptor(mMatmul.value) + .build(); + }) << "Builder failed to detect missing setA() call"; + EXPECT_ANY_THROW({ + OperationMatmulBuilder() + .setA(mA.value) + .setC(mC.value) + .setMatmulDescriptor(mMatmul.value) + .build(); + }) << "Builder failed to detect missing setB() call"; + EXPECT_ANY_THROW({ + OperationMatmulBuilder() + .setA(mA.value) + .setB(mB.value) + .setMatmulDescriptor(mMatmul.value) + .build(); + }) << "Builder failed to detect missing setC() call"; +} + +namespace { + +using miopen::graphapi::BackendMatmulDescriptor; +using miopen::graphapi::GMockBackendTensorDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +class GMockBackendMatmulDescriptor : public BackendMatmulDescriptor +{ +public: + GMockBackendMatmulDescriptor& operator=(const ValidatedValue& testCaseMatmul) + { + if(!testCaseMatmul.valid) + { + return *this; + } + + auto& theMatmul = *testCaseMatmul.value; + + auto compType = theMatmul.getComputeType(); + setAttribute(MIOPEN_ATTR_MATMUL_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &compType); + + finalize(); + + return *this; + } +}; + +} // namespace + +class GraphApiOperationMatmul : public ::testing::TestWithParam +{ +private: + // Pointers to these are stored in the objects below + GMockBackendMatmulDescriptor mMatmulDescriptor; + GMockBackendTensorDescriptor aDesc; + GMockBackendTensorDescriptor bDesc; + GMockBackendTensorDescriptor cDesc; + GMockBackendTensorDescriptor mOverrideDesc; + GMockBackendTensorDescriptor nOverrideDesc; + GMockBackendTensorDescriptor kOverrideDesc; + + // Pointers to these are stored in mExecute object below + GTestDescriptorSingleValueAttribute mMatmul; + GTestDescriptorSingleValueAttribute mA; + GTestDescriptorSingleValueAttribute mB; + GTestDescriptorSingleValueAttribute mC; + GTestDescriptorSingleValueAttribute mOverrideAttr; + GTestDescriptorSingleValueAttribute nOverrideAttr; + GTestDescriptorSingleValueAttribute kOverrideAttr; + +protected: + GTestGraphApiExecute mExecute; + + void SetUp() override + { + auto [valid, mamtul, a, b, c, count, mOverride, nOverride, kOverride] = GetParam(); + + try + { + mMatmulDescriptor = mamtul; + aDesc = a; + bDesc = b; + cDesc = c; + mOverrideDesc = mOverride; + nOverrideDesc = nOverride; + kOverrideDesc = kOverride; + } + catch(const std::exception& e) + { + FAIL() << e.what(); + } + + mMatmul = {mamtul.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_DESC", + MIOPEN_ATTR_OPERATION_MATMUL_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mMatmulDescriptor}; + + mA = {a.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_ADESC", + MIOPEN_ATTR_OPERATION_MATMUL_ADESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &aDesc}; + + mB = {b.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_BDESC", + MIOPEN_ATTR_OPERATION_MATMUL_BDESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &bDesc}; + + mC = {c.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_CDESC", + MIOPEN_ATTR_OPERATION_MATMUL_CDESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &cDesc}; + + mOverrideAttr = {mOverride.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_GEMM_M_OVERRIDE_DESC", + MIOPEN_ATTR_OPERATION_MATMUL_GEMM_M_OVERRIDE_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mOverrideDesc}; + + nOverrideAttr = {nOverride.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_GEMM_N_OVERRIDE_DESC", + MIOPEN_ATTR_OPERATION_MATMUL_GEMM_N_OVERRIDE_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &nOverrideDesc}; + + kOverrideAttr = {kOverride.valid, + "MIOPEN_ATTR_OPERATION_MATMUL_GEMM_K_OVERRIDE_DESC", + MIOPEN_ATTR_OPERATION_MATMUL_GEMM_K_OVERRIDE_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &kOverrideDesc}; + + mExecute.descriptor.attrsValid = valid; + mExecute.descriptor.textName = "MIOPEN_BACKEND_OPERATION_MATMUL_DESCRIPTOR"; + mExecute.descriptor.type = MIOPEN_BACKEND_OPERATION_MATMUL_DESCRIPTOR; + mExecute.descriptor.attributes = { + &mMatmul, &mA, &mB, &mC, &mOverrideAttr, &nOverrideAttr, &kOverrideAttr}; + } +}; + +TEST_P(GraphApiOperationMatmul, CFunctions) { mExecute(); } + +static Matmul matmul(miopenFloat); +static std::array, 2> anyMatmuls{ValidatedValue{true, &matmul}, + ValidatedValue{false, nullptr}}; +static std::array, 1> validMatmuls{ValidatedValue{true, &matmul}}; +static std::array, 1> invalidMatmuls{ + ValidatedValue{false, nullptr}}; + +static Tensor A(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); +static Tensor B(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); +static Tensor C(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); + +static Tensor mOverride(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); +static Tensor nOverride(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); +static Tensor kOverride(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); + +static Tensor D(miopenFloat, {10, 100}, {100, 1}, 1, false); +static Tensor E(miopenFloat, {2, 10, 100, 100}, {100 * 100 * 10, 100 * 100, 100, 1}, 1, false); + +static Tensor aSmall(miopenFloat, {100}, {1}, 1, false); +static Tensor bSmall(miopenFloat, {100}, {1}, 1, false); +static Tensor cSmall(miopenFloat, {100}, {1}, 1, false); + +static std::array, 3> anyAtensors{ValidatedValue{true, &A}, + ValidatedValue{false, nullptr}, + ValidatedValue{false, &aSmall}}; +static std::array, 1> validAtensors{ValidatedValue{true, &A}}; +static std::array, 2> invalidAtensors{ + ValidatedValue{false, nullptr}, ValidatedValue{false, &aSmall}}; + +static std::array, 3> anyBtensors{ValidatedValue{true, &B}, + ValidatedValue{false, nullptr}, + ValidatedValue{false, &bSmall}}; +static std::array, 1> validBtensors{ValidatedValue{true, &B}}; +static std::array, 2> invalidBtensors{ + ValidatedValue{false, nullptr}, ValidatedValue{false, &bSmall}}; + +static std::array, 3> anyCtensors{ValidatedValue{true, &C}, + ValidatedValue{false, nullptr}, + ValidatedValue{false, &cSmall}}; +static std::array, 1> validCtensors{ValidatedValue{true, &C}}; +static std::array, 2> invalidCtensors{ + ValidatedValue{false, nullptr}, ValidatedValue{false, &cSmall}}; + +static std::array, 1> validMOverridetensors{ + ValidatedValue{true, &mOverride}}; + +static std::array, 1> validNOverridetensors{ + ValidatedValue{true, &nOverride}}; + +static std::array, 1> validKOverridetensors{ + ValidatedValue{true, &kOverride}}; + +static std::array, 1> invalidBroadcastTensors{ + ValidatedValue{true, &D}}; + +static std::array, 1> validBroadcastTensors{ + ValidatedValue{true, &E}}; + +static auto validAttributes = testing::Combine(testing::Values(true), + testing::ValuesIn(validMatmuls), + testing::ValuesIn(validAtensors), + testing::ValuesIn(validBtensors), + testing::ValuesIn(validCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +static auto invalidBroadcasts = testing::Combine(testing::Values(false), + testing::ValuesIn(validMatmuls), + testing::ValuesIn(invalidBroadcastTensors), + testing::ValuesIn(validBtensors), + testing::ValuesIn(validCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +static auto validBroadcasts = testing::Combine(testing::Values(true), + testing::ValuesIn(validMatmuls), + testing::ValuesIn(validBroadcastTensors), + testing::ValuesIn(validBtensors), + testing::ValuesIn(validCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +static auto invalidAtLeastMatmuls = testing::Combine(testing::Values(false), + testing::ValuesIn(invalidMatmuls), + testing::ValuesIn(anyAtensors), + testing::ValuesIn(anyBtensors), + testing::ValuesIn(anyCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +static auto invalidAtLeastAtensors = testing::Combine(testing::Values(false), + testing::ValuesIn(anyMatmuls), + testing::ValuesIn(invalidAtensors), + testing::ValuesIn(anyBtensors), + testing::ValuesIn(anyCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +static auto invalidAtLeastBtensors = testing::Combine(testing::Values(false), + testing::ValuesIn(anyMatmuls), + testing::ValuesIn(anyAtensors), + testing::ValuesIn(invalidBtensors), + testing::ValuesIn(validCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +static auto invalidAtLeastCtensors = testing::Combine(testing::Values(false), + testing::ValuesIn(anyMatmuls), + testing::ValuesIn(anyAtensors), + testing::ValuesIn(anyBtensors), + testing::ValuesIn(invalidCtensors), + testing::Values(1), + testing::ValuesIn(validMOverridetensors), + testing::ValuesIn(validNOverridetensors), + testing::ValuesIn(validKOverridetensors)); + +INSTANTIATE_TEST_SUITE_P(ValidAttributes, GraphApiOperationMatmulBuilder, validAttributes); + +INSTANTIATE_TEST_SUITE_P(invalidBroadcasts, GraphApiOperationMatmulBuilder, invalidBroadcasts); + +INSTANTIATE_TEST_SUITE_P(invalidAtLeastMatmuls, + GraphApiOperationMatmulBuilder, + invalidAtLeastMatmuls); +INSTANTIATE_TEST_SUITE_P(invalidAtLeastAtensors, + GraphApiOperationMatmulBuilder, + invalidAtLeastAtensors); +INSTANTIATE_TEST_SUITE_P(invalidAtLeastBtensors, + GraphApiOperationMatmulBuilder, + invalidAtLeastBtensors); +INSTANTIATE_TEST_SUITE_P(invalidAtLeastCtensors, + GraphApiOperationMatmulBuilder, + invalidAtLeastCtensors); + +INSTANTIATE_TEST_SUITE_P(ValidAttributes, GraphApiOperationMatmul, validAttributes); +INSTANTIATE_TEST_SUITE_P(invalidAtLeastMatmuls, GraphApiOperationMatmul, invalidAtLeastMatmuls); +INSTANTIATE_TEST_SUITE_P(invalidAtLeastAtensors, GraphApiOperationMatmul, invalidAtLeastAtensors); +INSTANTIATE_TEST_SUITE_P(invalidAtLeastBtensors, GraphApiOperationMatmul, invalidAtLeastBtensors); diff --git a/test/gtest/graphapi_operation_pointwise.cpp b/test/gtest/graphapi_operation_pointwise.cpp new file mode 100644 index 0000000000..a44db5d012 --- /dev/null +++ b/test/gtest/graphapi_operation_pointwise.cpp @@ -0,0 +1,739 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +#include "graphapi_gtest_common.hpp" + +namespace { + +using miopen::graphapi::OperationPointwise; +using miopen::graphapi::OperationPointwiseBuilder; +using miopen::graphapi::Pointwise; +using miopen::graphapi::Tensor; + +using OneInputTuple = std::tuple; +using TwoInputTuple = std::tuple; +using ThreeInputTuple = std::tuple; + +} // namespace + +TEST(GraphApiOperationPointwiseBuilderSingleSetter, AnyAttribute) +{ + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setPointwise(nullptr); }) + << "OperationPointwiseBuilder::setPointwise failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setX(nullptr); }) + << "OperationPointwiseBuilder::setX failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setB(nullptr); }) + << "OperationPointwiseBuilder::setB failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setY(nullptr); }) + << "OperationPointwiseBuilder::setY failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setT(nullptr); }) + << "OperationPointwiseBuilder::setT failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setDx(nullptr); }) + << "OperationPointwiseBuilder::setDx failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setDy(nullptr); }) + << "OperationPointwiseBuilder::setDy failed on an invalid attribute"; + + Pointwise pointwise{MIOPEN_POINTWISE_ADD, miopenFloat}; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setPointwise(&pointwise); }) + << "OperationPointwiseBuilder::setPointwise failed on a valid attribute"; + + Tensor tensor{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 1, false}; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setX(&tensor); }) + << "OperationPointwiseBuilder::setX failed on a valid attribute"; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setB(&tensor); }) + << "OperationPointwiseBuilder::setB failed on a ivalid attribute"; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setY(&tensor); }) + << "OperationPointwiseBuilder::setY failed on a ivalid attribute"; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setT(&tensor); }) + << "OperationPointwiseBuilder::setT failed on a valid attribute"; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setDx(&tensor); }) + << "OperationPointwiseBuilder::setDx failed on a valid attribute"; + EXPECT_NO_THROW({ OperationPointwiseBuilder().setDy(&tensor); }) + << "OperationPointwiseBuilder::setDy failed on a valid attribute"; +} + +class GraphApiOperationPointwiseBuilderOneInput : public testing::TestWithParam +{ +protected: + bool mValid; + Pointwise* mPointwise; + Tensor* mX; + Tensor* mY; + + void SetUp() override { std::tie(mValid, mPointwise, mX, mY) = GetParam(); } +}; + +TEST_P(GraphApiOperationPointwiseBuilderOneInput, Test) +{ + if(mValid) + { + EXPECT_NO_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).build(); + }) << "Builder failed on valid attributes"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setY(mY) + .setAlpha1(0.4f) + .build(); + }) << "Builder failed on valid attributes with alpha1"; + } + else + { + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).build(); + }) << "Builder failed on invalid attributes"; + } + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setX(mX).setY(mY).build(); }) + << "Builder failed to detect missing setPointwise call"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setPointwise(mPointwise).setY(mY).build(); }) + << "Builder failed to detect missing setX call"; + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).build(); }) + << "Builder failed to detect missing setY call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).setB(mX).build(); + }) << "Builder failed to detect unwanted setB call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).setT(mX).build(); + }) << "Builder failed to detect unwanted setT call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).setDx(mX).build(); + }) << "Builder failed to detect unwanted setDx call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).setDy(mX).build(); + }) << "Builder failed to detect unwanted setDy call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setY(mY) + .setAlpha2(0.5f) + .build(); + }) << "Builder failed to detect unwanted setAlpha2 call"; +} + +class GraphApiOperationPointwiseBuilderTwoInput : public testing::TestWithParam +{ +protected: + bool mValid; + Pointwise* mPointwise; + Tensor* mX; + Tensor* mB; + Tensor* mY; + + void SetUp() override { std::tie(mValid, mPointwise, mX, mB, mY) = GetParam(); } +}; + +TEST_P(GraphApiOperationPointwiseBuilderTwoInput, Test) +{ + if(mValid) + { + EXPECT_NO_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setB(mB).setY(mY).build(); + }) << "Builder failed on valid attributes"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setAlpha1(0.4f) + .build(); + }) << "Builder failed on valid attributes with alpha1"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setAlpha2(0.7f) + .build(); + }) << "Builder failed on valid attributes with alpha2"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setAlpha1(0.3f) + .setAlpha2(0.8f) + .build(); + }) << "Builder failed on valid attributes with alpha1 and alpha2"; + } + else + { + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setB(mB).setY(mY).build(); + }) << "Builder failed on invalid attributes"; + } + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setX(mX).setB(mB).setY(mY).build(); }) + << "Builder failed to detect missing setPointwise call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setB(mB).setY(mY).build(); + }) << "Builder failed to detect missing setX call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).build(); + }) << "Builder failed to detect missing setB call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setB(mB).build(); + }) << "Builder failed to detect missing setY call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mX) + .build(); + }) << "Builder failed to detect unwanted setT call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setDx(mX) + .build(); + }) << "Builder failed to detect unwanted setDx call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setDy(mX) + .build(); + }) << "Builder failed to detect unwanted setDy call"; +} + +class GraphApiOperationPointwiseBuilderBwd : public testing::TestWithParam +{ +protected: + bool mValid; + Pointwise* mPointwise; + Tensor* mY; + Tensor* mDy; + Tensor* mDx; + + void SetUp() override { std::tie(mValid, mPointwise, mY, mDy, mDx) = GetParam(); } +}; + +TEST_P(GraphApiOperationPointwiseBuilderBwd, Test) +{ + if(mValid) + { + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .build(); + }) << "Builder failed on valid attributes"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .setAlpha1(0.4f) + .build(); + }) << "Builder failed on valid attributes with alpha1"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .setAlpha2(0.7f) + .build(); + }) << "Builder failed on valid attributes with alpha2"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .setAlpha1(0.3f) + .setAlpha2(0.8f) + .build(); + }) << "Builder failed on valid attributes with alpha1 and alpha2"; + } + else + { + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .build(); + }) << "Builder failed on invalid attributes"; + } + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setY(mY).setDy(mDy).setDx(mDx).build(); }) + << "Builder failed to detect missing setPointwise call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setDy(mDy).setDx(mDx).build(); + }) << "Builder failed to detect missing setY call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setY(mY).setDx(mDx).build(); + }) << "Builder failed to detect missing setDy call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setY(mY).setDy(mDy).build(); + }) << "Builder failed to detect missing setDx call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .setX(mY) + .build(); + }) << "Builder failed to detect unwanted setX call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .setB(mY) + .build(); + }) << "Builder failed to detect unwanted setB call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setY(mY) + .setDy(mDy) + .setDx(mDx) + .setT(mY) + .build(); + }) << "Builder failed to detect unwanted setT call"; +} + +class GraphApiOperationPointwiseBuilderThreeInput : public testing::TestWithParam +{ +protected: + bool mValid; + Pointwise* mPointwise; + Tensor* mX; + Tensor* mB; + Tensor* mY; + Tensor* mT; + + void SetUp() override { std::tie(mValid, mPointwise, mX, mB, mY, mT) = GetParam(); } +}; + +TEST_P(GraphApiOperationPointwiseBuilderThreeInput, Test) +{ + if(mValid) + { + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .build(); + }) << "Builder failed on valid attributes"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .setAlpha1(0.4f) + .build(); + }) << "Builder failed on valid attributes with alpha1"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .setAlpha2(0.6f) + .build(); + }) << "Builder failed on valid attributes with alpha2"; + EXPECT_NO_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .setAlpha1(0.3f) + .setAlpha1(0.7f) + .build(); + }) << "Builder failed on valid attributes with alpha1 and alpha2"; + } + else + { + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .build(); + }) << "Builder failed on invalid attributes"; + } + EXPECT_ANY_THROW({ OperationPointwiseBuilder().setX(mX).setB(mB).setY(mY).setT(mT).build(); }) + << "Builder failed to detect missing setPointwise call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setB(mB).setY(mY).setT(mT).build(); + }) << "Builder failed to detect missing setX call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setY(mY).setT(mT).build(); + }) << "Builder failed to detect missing setB call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setB(mB).setT(mT).build(); + }) << "Builder failed to detect missing setY call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder().setPointwise(mPointwise).setX(mX).setB(mB).setY(mY).build(); + }) << "Builder failed to detect missing setT call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .setDx(mX) + .build(); + }) << "Builder failed to detect unwanted setDx call"; + EXPECT_ANY_THROW({ + OperationPointwiseBuilder() + .setPointwise(mPointwise) + .setX(mX) + .setB(mB) + .setY(mY) + .setT(mT) + .setDy(mX) + .build(); + }) << "Builder failed to detect unwanted setDy call"; +} + +namespace { + +using miopen::graphapi::GMockBackendTensorDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; +using miopen::graphapi::ValidatedValue; + +class GMockBackendPointwiseDescriptor : public miopen::graphapi::BackendPointwiseDescriptor +{ +public: + GMockBackendPointwiseDescriptor& + operator=(Pointwise* pointwise) // we don't bother with ValidatedValue here + { + if(pointwise == nullptr) + { + return *this; + } + + auto mode = pointwise->getMode(); + setAttribute(MIOPEN_ATTR_POINTWISE_MODE, MIOPEN_TYPE_POINTWISE_MODE, 1, &mode); + + auto prec = pointwise->getMathPrecision(); + setAttribute(MIOPEN_ATTR_POINTWISE_MATH_PREC, MIOPEN_TYPE_DATA_TYPE, 1, &prec); + + finalize(); + + return *this; + } +}; + +class GraphApiOperationPointwiseBase +{ +private: + // Pointers to these are stored in the objects below + GMockBackendPointwiseDescriptor mPointwiseDescriptor; + std::vector mTensorDescriptors; + + // Pointers to these are stored in mExecute object below + GTestDescriptorSingleValueAttribute mPointwiseAttribute; + std::vector> + mTensorAttributes; + +protected: + GTestGraphApiExecute mExecute; + + void prepareExecute( + bool valid, + Pointwise* pointwise, + std::initializer_list> + tensors) + { + try + { + mTensorDescriptors.reserve(tensors.size()); // to prevent ptr invalidation + mTensorAttributes.reserve(tensors.size()); // to prevent ptr invalidation + mExecute.descriptor.attributes.reserve(tensors.size() + 1); + + mExecute.descriptor.textName = "MIOPEN_BACKEND_OPERATION_POINTWISE_DESCRIPTOR"; + mExecute.descriptor.type = MIOPEN_BACKEND_OPERATION_POINTWISE_DESCRIPTOR; + mExecute.descriptor.attrsValid = valid; + + mPointwiseDescriptor = pointwise; + mPointwiseAttribute = {pointwise != nullptr, + "MIOPEN_ATTR_OPERATION_POINTWISE_PW_DESCRIPTOR", + MIOPEN_ATTR_OPERATION_POINTWISE_PW_DESCRIPTOR, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mPointwiseDescriptor}; + mExecute.descriptor.attributes.push_back(&mPointwiseAttribute); + + std::for_each(tensors.begin(), tensors.end(), [this](const auto& tpl) { + auto ptr = std::get(tpl); + + auto& descriptor = mTensorDescriptors.emplace_back(); + descriptor = ValidatedValue{ptr != nullptr, ptr}; + + auto& attribute = + mTensorAttributes.emplace_back(ptr != nullptr, + std::get(tpl), + std::get(tpl), + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &descriptor); + mExecute.descriptor.attributes.push_back(&attribute); + }); + } + catch(const std::exception& e) + { + FAIL() << e.what(); + } + } +}; + +} // namespace + +class GraphApiOperationPointwiseOneInput : public testing::TestWithParam, + protected GraphApiOperationPointwiseBase +{ +protected: + void SetUp() override + { + auto [valid, pointwise, x, y] = GetParam(); + + // clang-format off + prepareExecute( + valid, + pointwise, + {{x, "MIOPEN_ATTR_OPERATION_POINTWISE_XDESC", MIOPEN_ATTR_OPERATION_POINTWISE_XDESC}, + {y, "MIOPEN_ATTR_OPERATION_POINTWISE_YDESC", MIOPEN_ATTR_OPERATION_POINTWISE_YDESC}}); + // clang-format on + } +}; + +class GraphApiOperationPointwiseTwoInput : public testing::TestWithParam, + protected GraphApiOperationPointwiseBase +{ +protected: + void SetUp() override + { + auto [valid, pointwise, x, b, y] = GetParam(); + + // clang-format off + prepareExecute( + valid, + pointwise, + {{x, "MIOPEN_ATTR_OPERATION_POINTWISE_XDESC", MIOPEN_ATTR_OPERATION_POINTWISE_XDESC}, + {b, "MIOPEN_ATTR_OPERATION_POINTWISE_BDESC", MIOPEN_ATTR_OPERATION_POINTWISE_BDESC}, + {y, "MIOPEN_ATTR_OPERATION_POINTWISE_YDESC", MIOPEN_ATTR_OPERATION_POINTWISE_YDESC}}); + // clang-format on + } +}; + +class GraphApiOperationPointwiseBwd : public testing::TestWithParam, + protected GraphApiOperationPointwiseBase +{ +protected: + void SetUp() override + { + auto [valid, pointwise, y, dy, dx] = GetParam(); + + // clang-format off + prepareExecute( + valid, + pointwise, + {{y, "MIOPEN_ATTR_OPERATION_POINTWISE_YDESC", MIOPEN_ATTR_OPERATION_POINTWISE_YDESC}, + {dy, "MIOPEN_ATTR_OPERATION_POINTWISE_DYDESC", MIOPEN_ATTR_OPERATION_POINTWISE_DYDESC}, + {dx, "MIOPEN_ATTR_OPERATION_POINTWISE_DXDESC", MIOPEN_ATTR_OPERATION_POINTWISE_DXDESC}}); + // clang-format on + } +}; + +class GraphApiOperationPointwiseThreeInput : public testing::TestWithParam, + protected GraphApiOperationPointwiseBase +{ +protected: + void SetUp() override + { + auto [valid, pointwise, x, b, y, t] = GetParam(); + + // clang-format off + prepareExecute( + valid, + pointwise, + {{x, "MIOPEN_ATTR_OPERATION_POINTWISE_XDESC", MIOPEN_ATTR_OPERATION_POINTWISE_XDESC}, + {b, "MIOPEN_ATTR_OPERATION_POINTWISE_BDESC", MIOPEN_ATTR_OPERATION_POINTWISE_BDESC}, + {y, "MIOPEN_ATTR_OPERATION_POINTWISE_YDESC", MIOPEN_ATTR_OPERATION_POINTWISE_YDESC}, + {t, "MIOPEN_ATTR_OPERATION_POINTWISE_TDESC", MIOPEN_ATTR_OPERATION_POINTWISE_TDESC}}); + // clang-format on + } +}; + +static Tensor x{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 1, false}; +static Tensor x2{miopenFloat, {8, 1, 64}, {64 * 1, 64, 1}, 2, false}; +static Tensor x3{miopenFloat, {8, 64, 1}, {64 * 1, 1, 1}, 3, false}; +static Tensor b{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 4, false}; +static Tensor b2{miopenFloat, {8, 1, 64}, {64 * 1, 64, 1}, 5, false}; +static Tensor b3{miopenFloat, {8, 64, 1}, {64 * 1, 1, 1}, 6, false}; +static Tensor y{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 7, false}; +static Tensor y2{miopenFloat, {8, 1, 64}, {64 * 1, 64, 1}, 8, false}; +static Tensor t{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 9, false}; +static Pointwise pointwiseAdd{MIOPEN_POINTWISE_ADD, miopenFloat}; +static Pointwise pointwiseAbs{MIOPEN_POINTWISE_ABS, miopenFloat}; +static Pointwise pointwiseReluBwd{MIOPEN_POINTWISE_RELU_BWD, miopenFloat}; +static Pointwise pointwiseBinSel{MIOPEN_POINTWISE_BINARY_SELECT, miopenFloat}; + +static auto oneInputValid = testing::Combine(testing::Values(true), + testing::Values(&pointwiseAbs), + testing::Values(&x), + testing::Values(&y)); +static auto oneInputInvalid = testing::Combine(testing::Values(false), + testing::Values(&pointwiseAbs, nullptr), + testing::Values(&x2, &x3, nullptr), + testing::Values(&y, nullptr)); + +static auto twoInputValid1 = testing::Combine(testing::Values(true), + testing::Values(&pointwiseAdd), + testing::Values(&x, &x2, &x3), + testing::Values(&b), + testing::Values(&y)); +static auto twoInputValid2 = testing::Combine(testing::Values(true), + testing::Values(&pointwiseAdd), + testing::Values(&x), + testing::Values(&b, &b2, &b3), + testing::Values(&y)); +static auto twoInputInvalid = testing::Combine(testing::Values(false), + testing::Values(&pointwiseAdd, nullptr), + testing::Values(&x, &x2, &x3, nullptr), + testing::Values(&b, nullptr), + testing::Values(&y2, nullptr)); + +static auto twoInputValidBwd1 = testing::Combine(testing::Values(true), + testing::Values(&pointwiseReluBwd), + testing::Values(&x, &x2, &x3), + testing::Values(&b), + testing::Values(&y)); +static auto twoInputValidBwd2 = testing::Combine(testing::Values(true), + testing::Values(&pointwiseReluBwd), + testing::Values(&x), + testing::Values(&b, &b2, &b3), + testing::Values(&y)); +static auto twoInputInvalidBwd = testing::Combine(testing::Values(false), + testing::Values(&pointwiseReluBwd, nullptr), + testing::Values(&x, &x2, &x3, nullptr), + testing::Values(&b, nullptr), + testing::Values(&y2, nullptr)); + +static auto threeInputValid = testing::Combine(testing::Values(true), + testing::Values(&pointwiseBinSel), + testing::Values(&x), + testing::Values(&b), + testing::Values(&y), + testing::Values(&t)); +static auto threeInputInvalid = testing::Combine(testing::Values(false), + testing::Values(&pointwiseBinSel, nullptr), + testing::Values(&x2, &x3, nullptr), + testing::Values(&b, &b2, &b3, nullptr), + testing::Values(&y, &y2, nullptr), + testing::Values(&t, nullptr)); + +INSTANTIATE_TEST_SUITE_P(OneInputValid, GraphApiOperationPointwiseBuilderOneInput, oneInputValid); +INSTANTIATE_TEST_SUITE_P(OneInputInvalid, + GraphApiOperationPointwiseBuilderOneInput, + oneInputInvalid); + +INSTANTIATE_TEST_SUITE_P(TwoInputValid1, GraphApiOperationPointwiseBuilderTwoInput, twoInputValid1); +INSTANTIATE_TEST_SUITE_P(TwoInputValid2, GraphApiOperationPointwiseBuilderTwoInput, twoInputValid2); +INSTANTIATE_TEST_SUITE_P(TwoInputInvalid, + GraphApiOperationPointwiseBuilderTwoInput, + twoInputInvalid); + +INSTANTIATE_TEST_SUITE_P(TwoInputValidBwd1, + GraphApiOperationPointwiseBuilderBwd, + twoInputValidBwd1); +INSTANTIATE_TEST_SUITE_P(TwoInputValidBwd2, + GraphApiOperationPointwiseBuilderBwd, + twoInputValidBwd2); +INSTANTIATE_TEST_SUITE_P(TwoInputInvalidBwd, + GraphApiOperationPointwiseBuilderBwd, + twoInputInvalidBwd); + +INSTANTIATE_TEST_SUITE_P(ThreeInputValid, + GraphApiOperationPointwiseBuilderThreeInput, + threeInputValid); +INSTANTIATE_TEST_SUITE_P(ThreeInputInvalid, + GraphApiOperationPointwiseBuilderThreeInput, + threeInputInvalid); + +TEST_P(GraphApiOperationPointwiseOneInput, CFunctions) { mExecute(); } +TEST_P(GraphApiOperationPointwiseTwoInput, CFunctions) { mExecute(); } +TEST_P(GraphApiOperationPointwiseBwd, CFunctions) { mExecute(); } +TEST_P(GraphApiOperationPointwiseThreeInput, CFunctions) { mExecute(); } + +INSTANTIATE_TEST_SUITE_P(OneInputValid, GraphApiOperationPointwiseOneInput, oneInputValid); +INSTANTIATE_TEST_SUITE_P(OneInputInvalid, GraphApiOperationPointwiseOneInput, oneInputInvalid); + +INSTANTIATE_TEST_SUITE_P(TwoInputValid1, GraphApiOperationPointwiseTwoInput, twoInputValid1); +INSTANTIATE_TEST_SUITE_P(TwoInputValid2, GraphApiOperationPointwiseTwoInput, twoInputValid2); +INSTANTIATE_TEST_SUITE_P(TwoInputInvalid, GraphApiOperationPointwiseTwoInput, twoInputInvalid); + +INSTANTIATE_TEST_SUITE_P(TwoInputValidBwd1, GraphApiOperationPointwiseBwd, twoInputValidBwd1); +INSTANTIATE_TEST_SUITE_P(TwoInputValidBwd2, GraphApiOperationPointwiseBwd, twoInputValidBwd2); +INSTANTIATE_TEST_SUITE_P(TwoInputInvalidBwd, GraphApiOperationPointwiseBwd, twoInputInvalidBwd); + +INSTANTIATE_TEST_SUITE_P(ThreeInputValid, GraphApiOperationPointwiseThreeInput, threeInputValid); +INSTANTIATE_TEST_SUITE_P(ThreeInputInvalid, + GraphApiOperationPointwiseThreeInput, + threeInputInvalid); diff --git a/test/gtest/graphapi_operation_reduction.cpp b/test/gtest/graphapi_operation_reduction.cpp new file mode 100644 index 0000000000..9c5f20ac96 --- /dev/null +++ b/test/gtest/graphapi_operation_reduction.cpp @@ -0,0 +1,268 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "graphapi_gtest_common.hpp" + +namespace { + +using miopen::graphapi::OperationReduction; +using miopen::graphapi::OperationReductionBuilder; +using miopen::graphapi::Reduction; +using miopen::graphapi::Tensor; + +} // namespace + +class GraphApiOperationReduction : public testing::Test +{ +protected: + Reduction mReduction{MIOPEN_REDUCE_TENSOR_ADD, miopenFloat}; + Tensor mX{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 1, false}; + Tensor mYs[5]{{miopenFloat, {8, 64, 64}, {64 * 64, 64, 1}, 2, false}, + {miopenFloat, {8, 1, 64}, {64, 64, 1}, 2, false}, + {miopenFloat, {8, 64, 1}, {64, 1, 1}, 2, false}, + {miopenFloat, {8, 1, 1}, {1, 1, 1}, 2, false}, + {miopenFloat, {8, 128, 1}, {128, 1, 1}, 2, false}}; + Tensor mBadY{miopenFloat, {8, 32, 32}, {32 * 32, 32, 1}, 2, false}; +}; + +TEST_F(GraphApiOperationReduction, Builder) +{ + for(Tensor& y : mYs) + { + EXPECT_NO_THROW({ + OperationReductionBuilder().setReduction(&mReduction).setX(&mX).setY(&y).build(); + }) << "Builder failed on valid attributes"; + } + EXPECT_ANY_THROW({ + OperationReductionBuilder().setReduction(&mReduction).setX(&mX).setY(&mBadY).build(); + }) << "Builder failed on invalid attributes"; + EXPECT_ANY_THROW({ OperationReductionBuilder().setReduction(nullptr); }) + << "OperationReductionBuilder::setReduction failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationReductionBuilder().setX(nullptr); }) + << "OperationReductionBuilder::setX failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationReductionBuilder().setY(nullptr); }) + << "OperationReductionBuilder::setY failed on an invalid attribute"; + EXPECT_ANY_THROW({ OperationReductionBuilder().setX(&mX).setY(mYs).build(); }) + << "Builder failed to detect missing setReduction call"; + EXPECT_ANY_THROW({ OperationReductionBuilder().setReduction(&mReduction).setY(mYs).build(); }) + << "Builder failed to detect missing setX call"; + EXPECT_ANY_THROW({ OperationReductionBuilder().setReduction(&mReduction).setX(&mX).build(); }) + << "Builder failed to detect missing setY call"; +} + +namespace { + +using miopen::graphapi::BackendReductionDescriptor; +using miopen::graphapi::GMockBackendTensorDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; +using miopen::graphapi::ValidatedValue; + +class GMockBackendReductionDescriptor : public BackendReductionDescriptor +{ +public: + GMockBackendReductionDescriptor& operator=(Reduction* reduction) + { + if(reduction == nullptr) + { + return *this; + } + + auto reductionOperator = reduction->getReductionOperator(); + setAttribute(MIOPEN_ATTR_REDUCTION_OPERATOR, + MIOPEN_TYPE_REDUCTION_OPERATOR_TYPE, + 1, + &reductionOperator); + + auto compType = reduction->getCompType(); + setAttribute(MIOPEN_ATTR_REDUCTION_COMP_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &compType); + + finalize(); + + return *this; + } +}; + +class ReductionAttribute + : public GTestDescriptorSingleValueAttribute +{ +private: + GMockBackendReductionDescriptor mReduction; + +public: + ReductionAttribute() + : GTestDescriptorSingleValueAttribute( + false, + "MIOPEN_ATTR_OPERATION_REDUCTION_DESC", + MIOPEN_ATTR_OPERATION_REDUCTION_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mReduction) + { + } + ReductionAttribute(Reduction* reduction) : ReductionAttribute() { *this = reduction; } + + ReductionAttribute& operator=(Reduction* reduction) + { + try + { + mReduction = reduction; + } + catch(...) + { + } + mTestCase.isCorrect = mReduction.isFinalized(); + return *this; + } +}; + +class XAttribute : public GTestDescriptorSingleValueAttribute +{ +private: + GMockBackendTensorDescriptor mTensor; + +public: + XAttribute() + : GTestDescriptorSingleValueAttribute( + false, + "MIOPEN_ATTR_OPERATION_REDUCTION_XDESC", + MIOPEN_ATTR_OPERATION_REDUCTION_XDESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mTensor) + { + } + XAttribute(Tensor* tensor) : XAttribute() { *this = tensor; } + + XAttribute& operator=(Tensor* tensor) + { + try + { + mTensor = ValidatedValue{tensor != nullptr, tensor}; + } + catch(...) + { + } + mTestCase.isCorrect = mTensor.isFinalized(); + return *this; + } +}; + +class YAttribute : public GTestDescriptorSingleValueAttribute +{ +private: + GMockBackendTensorDescriptor mTensor; + +public: + YAttribute() + : GTestDescriptorSingleValueAttribute( + false, + "MIOPEN_ATTR_OPERATION_REDUCTION_YDESC", + MIOPEN_ATTR_OPERATION_REDUCTION_YDESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mTensor) + { + } + YAttribute(Tensor* tensor) : YAttribute() { *this = tensor; } + + YAttribute& operator=(Tensor* tensor) + { + try + { + mTensor = ValidatedValue{tensor != nullptr, tensor}; + } + catch(...) + { + } + mTestCase.isCorrect = mTensor.isFinalized(); + return *this; + } +}; + +} // namespace + +TEST_F(GraphApiOperationReduction, CFunctions) +{ + ReductionAttribute invalidReduction; + + ReductionAttribute goodReduction(&mReduction); + + XAttribute invalidX; + YAttribute invalidY; + + XAttribute x(&mX); + + YAttribute ys[5]; + std::transform(std::begin(mYs), std::end(mYs), std::begin(ys), [](Tensor& tensor) -> Tensor* { + return &tensor; + }); + + YAttribute badY(&mBadY); + + GTestGraphApiExecute execute{ + {"MIOPEN_BACKEND_OPERATION_REDUCTION_DESCRIPTOR", + MIOPEN_BACKEND_OPERATION_REDUCTION_DESCRIPTOR, + true, + {}}}; + + for(auto& y : ys) + { + execute.descriptor.attributes = {&goodReduction, &x, &y}; + execute(); + } + + execute.descriptor.attrsValid = false; + + execute.descriptor.attributes = {&goodReduction, &x, &badY}; + execute(); + + execute.descriptor.attributes = {&invalidReduction}; + execute(); + + execute.descriptor.attributes = {&invalidX}; + execute(); + + execute.descriptor.attributes = {&invalidY}; + execute(); + + execute.descriptor.attributes = {&x, ys}; + execute(); + + execute.descriptor.attributes = {&goodReduction, ys}; + execute(); + + execute.descriptor.attributes = {&goodReduction, &x}; + execute(); +} diff --git a/test/gtest/graphapi_operation_rng.cpp b/test/gtest/graphapi_operation_rng.cpp new file mode 100644 index 0000000000..1e33331d26 --- /dev/null +++ b/test/gtest/graphapi_operation_rng.cpp @@ -0,0 +1,528 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 +#include + +#include + +#include "graphapi_gtest_common.hpp" + +namespace miopen { + +namespace graphapi { + +std::ostream& operator<<(std::ostream& os, std::variant seed) +{ + if(seed.index() == 0) + return os << std::get<0>(seed); + else + return os << std::get<1>(seed); +}; + +} // namespace graphapi + +} // namespace miopen + +namespace { + +using miopen::graphapi::OperationRng; +using miopen::graphapi::OperationRngBuilder; +using miopen::graphapi::Rng; +using miopen::graphapi::Tensor; + +using miopen::graphapi::ValidatedValue; + +using DescriptorTuple = std::tuple, + ValidatedValue, + ValidatedValue>, + ValidatedValue>; + +} // namespace + +class GraphApiOperationRngBuilder : public testing::TestWithParam +{ +protected: + bool mAttrsValid; + ValidatedValue mRng; + ValidatedValue mOutput; + ValidatedValue> mSeed; + ValidatedValue mOffset; + + void SetUp() override { std::tie(mAttrsValid, mRng, mOutput, mSeed, mOffset) = GetParam(); } + + OperationRng buildWithDefaultSeed() + { + return OperationRngBuilder() + .setRng(mRng.value) + .setOutput(mOutput.value) + .setOffset(mOffset.value) + .build(); + } + + OperationRng build() + { + if(mSeed.value.index() == 0) + { + return OperationRngBuilder() + .setRng(mRng.value) + .setOutput(mOutput.value) + .setSeed(std::get<0>(mSeed.value)) + .setOffset(mOffset.value) + .build(); + } + else + { + return OperationRngBuilder() + .setRng(mRng.value) + .setOutput(mOutput.value) + .setSeed(std::get<1>(mSeed.value)) + .setOffset(mOffset.value) + .build(); + } + } +}; + +TEST_P(GraphApiOperationRngBuilder, ValidateAttributes) +{ + if(mAttrsValid) + { + EXPECT_NO_THROW({ build(); }) << "Builder failed on valid attributes"; + EXPECT_NO_THROW({ buildWithDefaultSeed(); }) + << "Builder failed on valid attributes and default seed"; + } + else + { + EXPECT_ANY_THROW({ build(); }) << "Builder failed to detect invalid attributes"; + if(!mRng.valid || !mOutput.valid || !mOffset.valid) + { + EXPECT_ANY_THROW({ buildWithDefaultSeed(); }) + << "Builder failed to detect invalid attributes with default seed"; + } + } + + if(mRng.valid) + { + EXPECT_NO_THROW({ OperationRngBuilder().setRng(mRng.value); }) + << "OperationRngBuilder::setRng failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationRngBuilder().setRng(mRng.value); }) + << "OperationRngBuilder::setRng failed with an invalid attribute"; + } + + if(mOutput.valid) + { + EXPECT_NO_THROW({ OperationRngBuilder().setOutput(mOutput.value); }) + << "OperationRngBuilder::setOutput failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationRngBuilder().setOutput(mOutput.value); }) + << "OperationRngBuilder::setOutput failed with an invalid attribute"; + } + + if(mSeed.valid) + { + if(mSeed.value.index() == 0) + { + EXPECT_NO_THROW({ OperationRngBuilder().setSeed(std::get<0>(mSeed.value)); }) + << "OperationRngBuilder::setSeed(int64_t) failed with a valid attribute"; + } + else + { + EXPECT_NO_THROW({ OperationRngBuilder().setSeed(std::get<1>(mSeed.value)); }) + << "OperationRngBuilder::setSeed(Tensor*) failed with a valid attribute"; + } + } + else + { + if(mSeed.value.index() == 0) + { + EXPECT_ANY_THROW({ OperationRngBuilder().setSeed(std::get<0>(mSeed.value)); }) + << "OperationRngBuilder::setSeed(int64_t) failed with an invalid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationRngBuilder().setSeed(std::get<1>(mSeed.value)); }) + << "OperationRngBuilder::setSeed(Tensor*) failed with an invalid attribute"; + } + } + + if(mOffset.valid) + { + EXPECT_NO_THROW({ OperationRngBuilder().setOffset(mOffset.value); }) + << "OperationRngBuilder::setOffset failed with a valid attribute"; + } + else + { + EXPECT_ANY_THROW({ OperationRngBuilder().setOffset(mOffset.value); }) + << "OperationRngBuilder::setOffset failed with an invalid attribute"; + } +} + +TEST_P(GraphApiOperationRngBuilder, MissingSetter) +{ + EXPECT_ANY_THROW({ + OperationRngBuilder().setOutput(mOutput.value).setOffset(mOffset.value).build(); + }) << "Builder with default seed failed to detect missing setRng() call"; + EXPECT_ANY_THROW({ OperationRngBuilder().setRng(mRng.value).setOffset(mOffset.value).build(); }) + << "Builder with default seed failed to detect missing setOutput() call"; + EXPECT_ANY_THROW({ OperationRngBuilder().setRng(mRng.value).setOutput(mOutput.value).build(); }) + << "Builder with default seed failed to detect missing setOffset() call"; + if(mSeed.value.index() == 0) + { + EXPECT_ANY_THROW({ + OperationRngBuilder() + .setSeed(std::get<0>(mSeed.value)) + .setOutput(mOutput.value) + .setOffset(mOffset.value) + .build(); + }) << "Builder failed to detect missing setRng() call"; + EXPECT_ANY_THROW({ + OperationRngBuilder() + .setRng(mRng.value) + .setSeed(std::get<0>(mSeed.value)) + .setOffset(mOffset.value) + .build(); + }) << "Builder failed to detect missing setOutput() call"; + EXPECT_ANY_THROW({ + OperationRngBuilder() + .setRng(mRng.value) + .setSeed(std::get<0>(mSeed.value)) + .setOutput(mOutput.value) + .build(); + }) << "Builder failed to detect missing setOffset() call"; + } + else + { + EXPECT_ANY_THROW({ + OperationRngBuilder() + .setSeed(std::get<1>(mSeed.value)) + .setOutput(mOutput.value) + .setOffset(mOffset.value) + .build(); + }) << "Builder failed to detect missing setRng() call"; + EXPECT_ANY_THROW({ + OperationRngBuilder() + .setRng(mRng.value) + .setSeed(std::get<1>(mSeed.value)) + .setOffset(mOffset.value) + .build(); + }) << "Builder failed to detect missing setOutput() call"; + EXPECT_ANY_THROW({ + OperationRngBuilder() + .setRng(mRng.value) + .setSeed(std::get<1>(mSeed.value)) + .setOutput(mOutput.value) + .build(); + }) << "Builder failed to detect missing setOffset() call"; + } +} + +namespace { + +using miopen::graphapi::BackendRngDescriptor; +using miopen::graphapi::GMockBackendTensorDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +class Seed +{ +private: + std::variant, + GTestDescriptorSingleValueAttribute> + mAttribute; + GMockBackendTensorDescriptor mTensor; + +public: + Seed& operator=(ValidatedValue>& testCaseSeed) + { + constexpr const char* textName = "MIOPEN_ATTR_OPERATION_RNG_SEED"; + constexpr miopenBackendAttributeName_t name = MIOPEN_ATTR_OPERATION_RNG_SEED; + + if(testCaseSeed.value.index() == 0) + { + mAttribute = + GTestDescriptorSingleValueAttribute(testCaseSeed.valid, + textName, + name, + MIOPEN_TYPE_INT64, + MIOPEN_TYPE_CHAR, + 2, + std::get<0>(testCaseSeed.value)); + } + else + { + if(testCaseSeed.valid) + { + mTensor = *std::get<1>(testCaseSeed.value); + } + mAttribute = GTestDescriptorSingleValueAttribute( + testCaseSeed.valid, + textName, + name, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mTensor); + } + + return *this; + } + GTestDescriptorAttribute* get() + { + if(mAttribute.index() == 0) + { + return &std::get<0>(mAttribute); + } + else + { + return &std::get<1>(mAttribute); + } + } +}; + +class GMockBackendRngDescriptor : public BackendRngDescriptor +{ +public: + GMockBackendRngDescriptor& operator=(const ValidatedValue& testCaseRng) + { + if(!testCaseRng.valid) + { + return *this; + } + + auto& theRng = *testCaseRng.value; + + auto distr = theRng.getDistribution(); + setAttribute(MIOPEN_ATTR_RNG_DISTRIBUTION, MIOPEN_TYPE_RNG_DISTRIBUTION, 1, &distr); + + auto normalMean = theRng.getNormalMean(); + auto normalStdev = theRng.getNormalStdev(); + + auto uniformMin = theRng.getUniformMin(); + auto uniformMax = theRng.getUniformMax(); + + auto bernoulliProb = theRng.getBernoulliProb(); + + switch(distr) + { + case MIOPEN_RNG_DISTRIBUTION_NORMAL: + setAttribute(MIOPEN_ATTR_RNG_NORMAL_DIST_MEAN, MIOPEN_TYPE_DOUBLE, 1, &normalMean); + setAttribute(MIOPEN_ATTR_RNG_NORMAL_DIST_STANDARD_DEVIATION, + MIOPEN_TYPE_DOUBLE, + 1, + &normalStdev); + break; + + case MIOPEN_RNG_DISTRIBUTION_UNIFORM: + setAttribute(MIOPEN_ATTR_RNG_UNIFORM_DIST_MINIMUM, MIOPEN_TYPE_DOUBLE, 1, &uniformMin); + setAttribute(MIOPEN_ATTR_RNG_UNIFORM_DIST_MAXIMUM, MIOPEN_TYPE_DOUBLE, 1, &uniformMax); + break; + + case MIOPEN_RNG_DISTRIBUTION_BERNOULLI: + setAttribute( + MIOPEN_ATTR_RNG_BERNOULLI_DIST_PROBABILITY, MIOPEN_TYPE_DOUBLE, 1, &bernoulliProb); + break; + } + + finalize(); + + return *this; + } +}; + +} // namespace + +class GraphApiOperationRng : public ::testing::TestWithParam +{ +private: + // Pointers to these are stored in the objects below + GMockBackendRngDescriptor mRngDescriptor; + GMockBackendTensorDescriptor mOutputDescriptor; + GMockBackendTensorDescriptor mOffsetDesctiptor; + + // Pointers to these are stored in mExecute object below + GTestDescriptorSingleValueAttribute mRng; + GTestDescriptorSingleValueAttribute mOutput; + Seed mSeed; + GTestDescriptorSingleValueAttribute mOffset; + +protected: + GTestGraphApiExecute mExecute; + + void SetUp() override + { + auto [valid, rng, output, seed, offset] = GetParam(); + + try + { + mRngDescriptor = rng; + mOutputDescriptor = output; + mSeed = seed; + mOffsetDesctiptor = offset; + } + catch(const std::exception& e) + { + FAIL() << e.what(); + } + + mRng = {rng.valid, + "MIOPEN_ATTR_OPERATION_RNG_DESC", + MIOPEN_ATTR_OPERATION_RNG_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mRngDescriptor}; + + mOutput = {output.valid, + "MIOPEN_ATTR_OPERATION_RNG_YDESC", + MIOPEN_ATTR_OPERATION_RNG_YDESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mOutputDescriptor}; + + mOffset = {offset.valid, + "MIOPEN_ATTR_OPERATION_RNG_OFFSET_DESC", + MIOPEN_ATTR_OPERATION_RNG_OFFSET_DESC, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + 2, + &mOffsetDesctiptor}; + + mExecute.descriptor.attrsValid = valid; + mExecute.descriptor.textName = "MIOPEN_BACKEND_OPERATION_RNG_DESCRIPTOR"; + mExecute.descriptor.type = MIOPEN_BACKEND_OPERATION_RNG_DESCRIPTOR; + mExecute.descriptor.attributes = {&mRng, &mOutput, mSeed.get(), &mOffset}; + } +}; + +TEST_P(GraphApiOperationRng, CFunctions) { mExecute(); } + +static Rng anRng(MIOPEN_RNG_DISTRIBUTION_BERNOULLI, 0, 0, 0, 0, 0.5); + +static std::array, 2> anyRngs{ValidatedValue{true, &anRng}, + ValidatedValue{false, nullptr}}; + +static std::array, 1> validRngs{ValidatedValue{true, &anRng}}; + +static std::array, 1> invalidRngs{ValidatedValue{false, nullptr}}; + +static Tensor anOutput(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 1, false); + +static std::array, 2> anyOutputs{ValidatedValue{true, &anOutput}, + ValidatedValue{false, nullptr}}; + +static std::array, 1> validOutputs{ + ValidatedValue{true, &anOutput}}; + +static std::array, 1> invalidOutputs{ + ValidatedValue{false, nullptr}}; + +static Tensor aValidSeedOrOffset(miopenFloat, {1, 1, 1}, {1, 1, 1}, 2, false); +static Tensor anInvalidSeedOrOffset(miopenFloat, {10, 100, 100}, {100 * 100, 100, 1}, 3, false); + +static std::array>, 4> anySeeds{ + ValidatedValue>{true, 1}, + ValidatedValue>{true, &aValidSeedOrOffset}, + ValidatedValue>{false, nullptr}, + ValidatedValue>{false, &anInvalidSeedOrOffset}}; + +static std::array>, 2> validSeeds{ + ValidatedValue>{true, 1}, + ValidatedValue>{true, &aValidSeedOrOffset}}; + +static std::array>, 2> invalidSeeds{ + ValidatedValue>{false, nullptr}, + ValidatedValue>{false, &anInvalidSeedOrOffset}}; + +static std::array, 3> anyOffsets{ + ValidatedValue{true, &aValidSeedOrOffset}, + ValidatedValue{false, nullptr}, + ValidatedValue{false, &anInvalidSeedOrOffset}}; + +static std::array, 1> validOffsets{ + ValidatedValue{true, &aValidSeedOrOffset}}; + +static std::array, 2> invalidOffsets{ + ValidatedValue{false, nullptr}, + ValidatedValue{false, &anInvalidSeedOrOffset}}; + +static auto validAttributes = testing::Combine(testing::Values(true), + testing::ValuesIn(validRngs), + testing::ValuesIn(validOutputs), + testing::ValuesIn(validSeeds), + testing::ValuesIn(validOffsets)); + +static auto invalidAtLeastRngs = testing::Combine(testing::Values(false), + testing::ValuesIn(invalidRngs), + testing::ValuesIn(anyOutputs), + testing::ValuesIn(anySeeds), + testing::ValuesIn(anyOffsets)); + +static auto invalidAtLeastOutputs = testing::Combine(testing::Values(false), + testing::ValuesIn(anyRngs), + testing::ValuesIn(invalidOutputs), + testing::ValuesIn(anySeeds), + testing::ValuesIn(anyOffsets)); + +static auto invalidAtLeastSeeds = testing::Combine(testing::Values(false), + testing::ValuesIn(anyRngs), + testing::ValuesIn(anyOutputs), + testing::ValuesIn(invalidSeeds), + testing::ValuesIn(anyOffsets)); + +static auto invalidAtLeastOffsets = testing::Combine(testing::Values(false), + testing::ValuesIn(anyRngs), + testing::ValuesIn(anyOutputs), + testing::ValuesIn(anySeeds), + testing::ValuesIn(invalidOffsets)); + +INSTANTIATE_TEST_SUITE_P(ValidAttributes, GraphApiOperationRngBuilder, validAttributes); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastRngs, GraphApiOperationRngBuilder, invalidAtLeastRngs); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastOutputs, GraphApiOperationRngBuilder, invalidAtLeastOutputs); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastSeeds, GraphApiOperationRngBuilder, invalidAtLeastSeeds); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastOffsets, GraphApiOperationRngBuilder, invalidAtLeastOffsets); + +INSTANTIATE_TEST_SUITE_P(ValidAttributes, GraphApiOperationRng, validAttributes); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastRngs, GraphApiOperationRng, invalidAtLeastRngs); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastOutputs, GraphApiOperationRng, invalidAtLeastOutputs); +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastOffsets, GraphApiOperationRng, invalidAtLeastOffsets); + +/* This one won't work as intended because seed is an optional attribute with a default value + * and Graph API allows to finalize() if other attributes are valid. + +INSTANTIATE_TEST_SUITE_P(InvalidAtLeastSeeds, GraphApiOperationRng, invalidAtLeastSeeds); +*/ diff --git a/test/gtest/graphapi_operationgraph_descriptor.cpp b/test/gtest/graphapi_operationgraph_descriptor.cpp new file mode 100644 index 0000000000..d68eab9399 --- /dev/null +++ b/test/gtest/graphapi_operationgraph_descriptor.cpp @@ -0,0 +1,179 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +#include + +#include "graphapi_gtest_common.hpp" + +namespace { + +using miopen::graphapi::BackendDescriptor; +using miopen::graphapi::OpNode; +using miopen::graphapi::Tensor; + +static int64_t id = 0; + +class GMockNode : public OpNode +{ +private: + std::shared_ptr mIn; + std::shared_ptr mOut; + +public: + GMockNode() + : mIn(std::make_shared(miopenFloat, + std::vector{8, 64, 64}, + std::vector{64 * 64, 64, 1}, + ++id, + false)), + mOut(std::make_shared(miopenFloat, + std::vector{8, 64, 64}, + std::vector{64 * 64, 64, 1}, + ++id, + false)) + { + } + + std::vector getInTensors() const override { return {mIn.get()}; } + + std::vector getOutTensors() const override { return {mOut.get()}; } + + const std::string& signName() const override + { + static const std::string name = "OP_MOCK"; + return name; + } +}; + +class GMockBackendOperationDescriptor : public BackendDescriptor +{ +private: + GMockNode mNode; + +public: + GMockBackendOperationDescriptor(bool finalized) { mFinalized = finalized; } + void setAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t elementCount, + void* arrayOfElements) override + { + } + void finalize() override {} + void getAttribute(miopenBackendAttributeName_t attributeName, + miopenBackendAttributeType_t attributeType, + int64_t requestedElementCount, + int64_t* elementCount, + void* arrayOfElements) override + { + } + OpNode* getOperation() override { return &mNode; } +}; + +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestDescriptorVectorAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +} // namespace + +TEST(GraphApiOperationGraphDescriptor, CFunctions) +{ + miopenHandle_t handle = nullptr; + auto status = miopenCreate(&handle); + ASSERT_EQ(status, miopenStatusSuccess) << "Handle wasn't obtained, cannot continue"; + + GTestDescriptorSingleValueAttribute handleAttribute{ + true, + "MIOPEN_ATTR_OPERATIONGRAPH_HANDLE", + MIOPEN_ATTR_OPERATIONGRAPH_HANDLE, + MIOPEN_TYPE_HANDLE, + MIOPEN_TYPE_CHAR, + 0, + handle}; + + GMockBackendOperationDescriptor goodNode(true); + + GTestDescriptorVectorAttribute goodOp{ + true, + "MIOPEN_ATTR_OPERATIONGRAPH_OPS", + MIOPEN_ATTR_OPERATIONGRAPH_OPS, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + -1, + {&goodNode}}; + ; + + GTestGraphApiExecute execute{ + {"MIOPEN_BACKEND_OPERATIONGRAPH_DESCRIPTOR", + MIOPEN_BACKEND_OPERATIONGRAPH_DESCRIPTOR, + true, + {&handleAttribute, &goodOp}}}; + + execute(); + + execute.descriptor.attributes = {&handleAttribute, &goodOp, &goodOp}; + execute(); + + GMockBackendOperationDescriptor badNode(false); + + GTestDescriptorVectorAttribute badOp{ + false, + "MIOPEN_ATTR_OPERATIONGRAPH_OPS", + MIOPEN_ATTR_OPERATIONGRAPH_OPS, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + -1, + {&badNode}}; + ; + + execute.descriptor.attrsValid = false; + + execute.descriptor.attributes = {&handleAttribute}; + execute(); + + execute.descriptor.attributes = {&goodOp}; + execute(); + + execute.descriptor.attributes = {&handleAttribute, &badOp}; + execute(); + + badOp = {false, + "MIOPEN_ATTR_OPERATIONGRAPH_OPS", + MIOPEN_ATTR_OPERATIONGRAPH_OPS, + MIOPEN_TYPE_BACKEND_DESCRIPTOR, + MIOPEN_TYPE_CHAR, + -1, + {&goodNode, &goodNode}}; + execute(); + + miopenDestroy(handle); +} diff --git a/test/gtest/graphapi_opgraph.cpp b/test/gtest/graphapi_opgraph.cpp new file mode 100644 index 0000000000..ccdce76545 --- /dev/null +++ b/test/gtest/graphapi_opgraph.cpp @@ -0,0 +1,97 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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 "graphapi_opgraph_common.hpp" + +TEST(GraphAPI, BuildDiamond) +{ + using namespace graphapi_opgraph_tests; + + /* + * | + * | t_in + * v + * Top + * t_a / \ t_b + * / \ + * v v + * Left Right + * \ / + * t_c \ / t_d + * v v + * Bottom + * | + * |t_out + * v + */ + + auto t_in = makeDummyTensor("t_in"); + auto t_out = makeDummyTensor("t_out"); + + auto t_a = makeDummyTensor("t_a"); + auto t_b = makeDummyTensor("t_b"); + auto t_c = makeDummyTensor("t_c"); + auto t_d = makeDummyTensor("t_d"); + + miopenHandle_t handle = nullptr; + miopenStatus_t status = miopenCreate(&handle); + + ASSERT_EQ(status, miopenStatusSuccess) << "Handle wasn't created"; + ASSERT_NE(handle, nullptr) << "miopenCreate() created a null handle"; + + gr::OpGraphBuilder graph_builder; + + graph_builder.setHandle(handle); + + DummyNode top{"top", {&t_in}, {&t_a, &t_b}}; + DummyNode left{"left", {&t_a}, {&t_c}}; + DummyNode right{"right", {&t_b}, {&t_d}}; + DummyNode bottom{"bottom", {&t_c, &t_d}, {&t_out}}; + + graph_builder.addNode(&top); + graph_builder.addNode(&left); + graph_builder.addNode(&right); + graph_builder.addNode(&bottom); + + gr::OpGraph graph = std::move(graph_builder).build(); + + ASSERT_TRUE(graph.hasNode(&top)); + ASSERT_TRUE(graph.hasNode(&left)); + ASSERT_TRUE(graph.hasNode(&right)); + ASSERT_TRUE(graph.hasNode(&bottom)); + + ASSERT_TRUE(graph.hasEdge(&top, &t_a, &left)); + ASSERT_TRUE(graph.hasEdge(&top, &t_b, &right)); + ASSERT_TRUE(graph.hasEdge(&left, &t_c, &bottom)); + ASSERT_TRUE(graph.hasEdge(&right, &t_d, &bottom)); + + ASSERT_TRUE(graph.numNodes() == 4); + ASSERT_TRUE(graph.numEdges() == 4); + ASSERT_TRUE(graph.hasEdgeToSink(&bottom, &t_out)); + ASSERT_TRUE(graph.hasEdgeFromSource(&top, &t_in)); + + miopenDestroy(handle); +} diff --git a/test/gtest/graphapi_opgraph_common.hpp b/test/gtest/graphapi_opgraph_common.hpp new file mode 100644 index 0000000000..30da332060 --- /dev/null +++ b/test/gtest/graphapi_opgraph_common.hpp @@ -0,0 +1,170 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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. + * + *******************************************************************************/ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace graphapi_opgraph_tests { + +namespace gr = miopen::graphapi; + +inline gr::Tensor makeDummyTensor(std::string_view name) +{ + int64_t id = 0; + MIOPEN_THROW_IF(name.size() > sizeof(id), "tensor name exceeds 8 chars"); + std::copy_n(name.begin(), std::min(sizeof(id), name.size()), reinterpret_cast(&id)); + + return gr::TensorBuilder{} + .setDataType(miopenFloat) + .setDim({1}) + .setStride({1}) + .setId(id) + .setVirtual(true) + .build(); +} + +struct DummyNode : public gr::OpNode +{ + std::string mName; + std::vector mInTensors; + std::vector mOutTensors; + + DummyNode(const std::string& name, + const std::vector& ins, + const std::vector& outs) + : mName(name), mInTensors(ins), mOutTensors(outs) + { + } + + const std::string& signName() const final { return mName; } + + std::vector getInTensors() const final { return mInTensors; } + + std::vector getOutTensors() const final { return mOutTensors; } +}; + +struct DummyOpGraphGenerator +{ + + struct DummyNodeGenSpec + { + std::string mName; + std::vector mInTensors; + std::vector mOutTensors; + }; + +private: + std::unordered_map mTensorMap; + std::vector mNodes; + gr::OpGraph mGraph; + miopenHandle_t mHandle = nullptr; + + DummyOpGraphGenerator(const std::vector& node_specs) + { + miopenCreate(&mHandle); + + for(const auto& ns : node_specs) + { + std::vector in_tensors; + + for(const auto& ti : ns.mInTensors) + { + auto [it, flag] = mTensorMap.try_emplace(ti, makeDummyTensor(ti)); + in_tensors.emplace_back(&(it->second)); + } + + std::vector out_tensors; + for(const auto& to : ns.mOutTensors) + { + auto [it, flag] = mTensorMap.try_emplace(to, makeDummyTensor(to)); + out_tensors.emplace_back(&(it->second)); + } + + mNodes.emplace_back(ns.mName, in_tensors, out_tensors); + } + + gr::OpGraphBuilder builder; + + builder.setHandle(mHandle); + + for(auto& n : mNodes) + { + builder.addNode(&n); + } + + mGraph = std::move(builder).build(); + } + +public: + DummyOpGraphGenerator(const DummyOpGraphGenerator&) = delete; + DummyOpGraphGenerator(DummyOpGraphGenerator&&) = delete; + DummyOpGraphGenerator& operator=(const DummyOpGraphGenerator&) = delete; + DummyOpGraphGenerator& operator=(DummyOpGraphGenerator&&) = delete; + + ~DummyOpGraphGenerator() { miopenDestroy(mHandle); } + + static std::unique_ptr + Make(const std::vector& node_specs) + { + return std::unique_ptr(new DummyOpGraphGenerator(node_specs)); + } + + const auto& graph() const { return mGraph; } +}; + +inline std::unique_ptr makeDiamondGraph() +{ + /* + * | + * | t_in + * v + * Top + * t_a / \ t_b + * / \ + * v v + * Left Right + * \ / + * t_c \ / t_d + * v v + * Bottom + * | + * |t_out + * v + */ + + return DummyOpGraphGenerator::Make({{"top", {"t_in"}, {"t_a", "t_b"}}, + {"left", {"t_a"}, {"t_c"}}, + {"right", {"t_b"}, {"t_d"}}, + {"bottom", {"t_c", "t_d"}, {"t_out"}}}); +} + +} // end namespace graphapi_opgraph_tests diff --git a/test/gtest/graphapi_opgraph_matching.cpp b/test/gtest/graphapi_opgraph_matching.cpp new file mode 100644 index 0000000000..6930839a6c --- /dev/null +++ b/test/gtest/graphapi_opgraph_matching.cpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2023 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 "graphapi_opgraph_common.hpp" + +TEST(GraphMatchingAPI, DiamondGraphMatch) +{ + using namespace graphapi_opgraph_tests; + + auto dg1 = makeDiamondGraph(); + + ASSERT_TRUE(gr::isIsomorphic(dg1->graph(), dg1->graph())); + + { + // create identical copy + auto dg2 = makeDiamondGraph(); + + ASSERT_TRUE(gr::isIsomorphic(dg1->graph(), dg2->graph())); + } + + { + // create a mirror copy + auto dg3 = DummyOpGraphGenerator::Make({{"top", {"t_in"}, {"t_a", "t_b"}}, + {"left", {"t_b"}, {"t_d"}}, + {"right", {"t_a"}, {"t_c"}}, + {"bottom", {"t_c", "t_d"}, {"t_out"}}}); + + ASSERT_TRUE(gr::isIsomorphic(dg1->graph(), dg3->graph())); + } + + { + // remove one of the edges to bottom + auto dg4 = DummyOpGraphGenerator::Make({{"top", {"t_in"}, {"t_a", "t_b"}}, + {"left", {"t_b"}, {"t_d"}}, + {"right", {"t_a"}, {"t_c"}}, + {"bottom", {"t_c"}, {"t_out"}}}); + ASSERT_FALSE(gr::isIsomorphic(dg1->graph(), dg4->graph())); + } + + { + // remove one of the edges out of top + auto dg5 = DummyOpGraphGenerator::Make({{"top", {"t_in"}, {"t_a"}}, + {"left", {"t_b"}, {"t_d"}}, + {"right", {"t_a"}, {"t_c"}}, + {"bottom", {"t_c", "t_d"}, {"t_out"}}}); + ASSERT_FALSE(gr::isIsomorphic(dg1->graph(), dg5->graph())); + } +} diff --git a/test/gtest/graphapi_pointwise.cpp b/test/gtest/graphapi_pointwise.cpp new file mode 100644 index 0000000000..47659783c7 --- /dev/null +++ b/test/gtest/graphapi_pointwise.cpp @@ -0,0 +1,295 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +#include "graphapi_gtest_common.hpp" + +TEST(GraphApiPointwiseBuilder, Attributes) +{ + EXPECT_ANY_THROW({ + miopen::graphapi::PointwiseBuilder().setMode(MIOPEN_POINTWISE_ADD).build(); + }) << "Builder produced Pointwise despite missing setMathPrecision() call"; + EXPECT_ANY_THROW({ + miopen::graphapi::PointwiseBuilder().setMathPrecision(miopenFloat).build(); + }) << "Builder produced Pointwise despite missing setMode() call"; + EXPECT_NO_THROW({ + miopen::graphapi::PointwiseBuilder() + .setMode(MIOPEN_POINTWISE_ADD) + .setMathPrecision(miopenFloat) + .build(); + }) << "Builder failed to produce Pointwise with valid attributes"; +} + +namespace { + +using miopen::graphapi::GTestDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +class Mode : public GTestDescriptorSingleValueAttribute +{ +public: + Mode() = default; + Mode(miopenPointwiseMode_t mode) + : GTestDescriptorSingleValueAttribute( + true, + "MIOPEN_ATTR_POINTWISE_MODE", + MIOPEN_ATTR_POINTWISE_MODE, + MIOPEN_TYPE_POINTWISE_MODE, + MIOPEN_TYPE_CHAR, + 2, + mode) + { + } +}; + +class Precision : public GTestDescriptorSingleValueAttribute +{ +public: + Precision() = default; + Precision(miopenDataType_t precision) + : GTestDescriptorSingleValueAttribute( + true, + "MIOPEN_ATTR_POINTWISE_MATH_PREC", + MIOPEN_ATTR_POINTWISE_MATH_PREC, + MIOPEN_TYPE_DATA_TYPE, + MIOPEN_TYPE_CHAR, + 2, + precision) + { + } +}; + +class NanPropagation : public GTestDescriptorSingleValueAttribute +{ +public: + NanPropagation() = default; + NanPropagation(miopenNanPropagation_t value) + : GTestDescriptorSingleValueAttribute( + true, + "MIOPEN_ATTR_POINTWISE_NAN_PROPAGATION", + MIOPEN_ATTR_POINTWISE_NAN_PROPAGATION, + MIOPEN_TYPE_NAN_PROPOGATION, + MIOPEN_TYPE_CHAR, + 2, + value) + { + } +}; + +class DoubleAttribute : public GTestDescriptorSingleValueAttribute +{ +public: + DoubleAttribute() = default; + DoubleAttribute(const char* textName, miopenBackendAttributeName_t name) + : GTestDescriptorSingleValueAttribute( + true, textName, name, MIOPEN_TYPE_DOUBLE, MIOPEN_TYPE_CHAR, 2, 0.7) + { + } +}; + +class FloatAttribute : public GTestDescriptorSingleValueAttribute +{ +public: + FloatAttribute(const char* textName, miopenBackendAttributeName_t name) + : GTestDescriptorSingleValueAttribute( + true, textName, name, MIOPEN_TYPE_DOUBLE, MIOPEN_TYPE_CHAR, 2, 0.7f) + { + } +}; + +class Axis : public GTestDescriptorSingleValueAttribute +{ +public: + Axis() = default; + Axis(int64_t value) + : GTestDescriptorSingleValueAttribute(true, + "MIOPEN_ATTR_POINTWISE_AXIS", + MIOPEN_ATTR_POINTWISE_AXIS, + MIOPEN_TYPE_INT64, + MIOPEN_TYPE_CHAR, + 2, + value) + { + } +}; + +class DoubleOrFloatAttribute +{ +private: + std::variant mAttribute; + +public: + DoubleOrFloatAttribute() = default; + + void set(bool isDouble, const char* textName, miopenBackendAttributeName_t name) + { + if(isDouble) + { + mAttribute = DoubleAttribute(textName, name); + } + else + { + mAttribute = FloatAttribute(textName, name); + } + } + + GTestDescriptorAttribute* get() + { + if(mAttribute.index() == 0) + { + return &std::get<0>(mAttribute); + } + else + { + return &std::get<1>(mAttribute); + } + } +}; + +} // namespace + +using TestCaseType = std::tuple; + +void PrintTo(const TestCaseType& v, std::ostream* os) +{ + *os << "mode: " << std::get<0>(v) << ", prec: " << std::get<1>(v) + << ", nan: " << (std::get<2>(v) == MIOPEN_PROPAGATE_NAN ? "propagage" : "not propagate") + << ", relu_lower_clip: " << (std::get<3>(v) ? "double" : "float") + << ", relu_upper_clip: " << (std::get<4>(v) ? "double" : "float") + << ", relu_lower_clip_slope: " << (std::get<5>(v) ? "double" : "float") + << ", elu_alpha: " << (std::get<6>(v) ? "double" : "float") + << ", softplus_beta: " << (std::get<7>(v) ? "double" : "float") + << ", swish_beta: " << (std::get<8>(v) ? "double" : "float") + << ", axis: " << std::get<9>(v); +} + +class GraphApiPointwise : public testing::TestWithParam +{ +private: + Mode mMode; + Precision mPrecision; + NanPropagation mNanPropagation; + DoubleOrFloatAttribute mReluLowerClip; + DoubleOrFloatAttribute mReluUpperClip; + DoubleOrFloatAttribute mReluLowerClipSlope; + DoubleOrFloatAttribute mEluAlpha; + DoubleOrFloatAttribute mSoftPlusBeta; + DoubleOrFloatAttribute mSwishBeta; + Axis mAxis; + +protected: + GTestGraphApiExecute mExecute; + + void SetUp() override + { + auto [mode, + precision, + nanPropagation, + isReluLowerClipDouble, + isReluUpperClipDouble, + isReluLowerClipSlopeDouble, + isEluAlphaDouble, + isSoftPlusBetaDouble, + isSwishBetaDouble, + axis] = GetParam(); + + mMode = {mode}; + mPrecision = {precision}; + mNanPropagation = {nanPropagation}; + mAxis = axis; + + mReluLowerClip.set(isReluLowerClipDouble, + "MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP", + MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP); + + mReluUpperClip.set(isReluUpperClipDouble, + "MIOPEN_ATTR_POINTWISE_RELU_UPPER_CLIP", + MIOPEN_ATTR_POINTWISE_RELU_UPPER_CLIP); + + mReluLowerClipSlope.set(isReluLowerClipSlopeDouble, + "MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP_SLOPE", + MIOPEN_ATTR_POINTWISE_RELU_LOWER_CLIP_SLOPE); + + mEluAlpha.set( + isEluAlphaDouble, "MIOPEN_ATTR_POINTWISE_ELU_ALPHA", MIOPEN_ATTR_POINTWISE_ELU_ALPHA); + + mSoftPlusBeta.set(isSoftPlusBetaDouble, + "MIOPEN_ATTR_POINTWISE_SOFTPLUS_BETA", + MIOPEN_ATTR_POINTWISE_SOFTPLUS_BETA); + + mSwishBeta.set(isSwishBetaDouble, + "MIOPEN_ATTR_POINTWISE_SWISH_BETA", + MIOPEN_ATTR_POINTWISE_SWISH_BETA); + + mExecute.descriptor.attributes = {&mMode, + &mPrecision, + &mNanPropagation, + mReluLowerClip.get(), + mReluUpperClip.get(), + mReluLowerClipSlope.get(), + mEluAlpha.get(), + mSoftPlusBeta.get(), + mSwishBeta.get(), + &mAxis}; + + mExecute.descriptor.attrsValid = true; + mExecute.descriptor.textName = "MIOPEN_BACKEND_POINTWISE_DESCRIPTOR"; + mExecute.descriptor.type = MIOPEN_BACKEND_POINTWISE_DESCRIPTOR; + } +}; + +TEST_P(GraphApiPointwise, CFunctions) { mExecute(); } + +INSTANTIATE_TEST_SUITE_P( + ValidAttributes, + GraphApiPointwise, + testing::Combine(testing::Values(MIOPEN_POINTWISE_ADD, MIOPEN_POINTWISE_MUL), + testing::Values(miopenFloat, miopenHalf), + testing::Values(MIOPEN_NOT_PROPAGATE_NAN, MIOPEN_PROPAGATE_NAN), + testing::Values(true, false), + testing::Values(true, false), + testing::Values(true, false), + testing::Values(true, false), + testing::Values(true, false), + testing::Values(true, false), + testing::Values(-1, 0))); diff --git a/test/gtest/graphapi_reduction.cpp b/test/gtest/graphapi_reduction.cpp new file mode 100644 index 0000000000..30040ce9b4 --- /dev/null +++ b/test/gtest/graphapi_reduction.cpp @@ -0,0 +1,116 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "graphapi_gtest_common.hpp" + +namespace { + +using DescriptorTuple = std::tuple; +using miopen::graphapi::Reduction; +using miopen::graphapi::ReductionBuilder; + +} // namespace + +class GraphApiReductionBuilder : public testing::TestWithParam +{ +protected: + miopenReduceTensorOp_t mReductionOperator; + miopenDataType_t mCompType; + + void SetUp() override { std::tie(mReductionOperator, mCompType) = GetParam(); } +}; + +TEST_P(GraphApiReductionBuilder, MissingSetter) +{ + EXPECT_NO_THROW({ + ReductionBuilder().setReductionOperator(mReductionOperator).setCompType(mCompType).build(); + }) << "Builder failed on valid attributes"; + EXPECT_ANY_THROW({ ReductionBuilder().setCompType(mCompType).build(); }) + << "Builder validated attributes despite missing setReductionOperator() call"; + EXPECT_ANY_THROW({ ReductionBuilder().setReductionOperator(mReductionOperator).build(); }) + << "Builder validated attributes despite missing setCompType() call"; +} + +namespace { + +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +} // namespace + +class GraphApiReduction : public testing::TestWithParam +{ +private: + // Pointers to these are stored inside 'mExecute' object (below) + GTestDescriptorSingleValueAttribute mReductionOperator; + GTestDescriptorSingleValueAttribute mCompType; + +protected: + GTestGraphApiExecute mExecute; + + void SetUp() override + { + auto [reductionOperator, compType] = GetParam(); + + mReductionOperator = {true, + "MIOPEN_ATTR_REDUCTION_OPERATOR", + MIOPEN_ATTR_REDUCTION_OPERATOR, + MIOPEN_TYPE_REDUCTION_OPERATOR_TYPE, + MIOPEN_TYPE_CHAR, + 2, + reductionOperator}; + + mCompType = {true, + "MIOPEN_ATTR_REDUCTION_COMP_TYPE", + MIOPEN_ATTR_REDUCTION_COMP_TYPE, + MIOPEN_TYPE_DATA_TYPE, + MIOPEN_TYPE_CHAR, + 2, + compType}; + + mExecute.descriptor.attributes = {&mReductionOperator, &mCompType}; + + mExecute.descriptor.attrsValid = true; + mExecute.descriptor.textName = "MIOPEN_BACKEND_REDUCTION_DESCRIPTOR"; + mExecute.descriptor.type = MIOPEN_BACKEND_REDUCTION_DESCRIPTOR; + } +}; + +TEST_P(GraphApiReduction, CFunctions) { mExecute(); } + +static auto testCases = + testing::Combine(testing::Values(MIOPEN_REDUCE_TENSOR_ADD, MIOPEN_REDUCE_TENSOR_MUL), + testing::Values(miopenFloat, miopenHalf)); + +INSTANTIATE_TEST_SUITE_P(TestCases, GraphApiReductionBuilder, testCases); +INSTANTIATE_TEST_SUITE_P(TestCases, GraphApiReduction, testCases); diff --git a/test/gtest/graphapi_rng.cpp b/test/gtest/graphapi_rng.cpp new file mode 100644 index 0000000000..de5241c509 --- /dev/null +++ b/test/gtest/graphapi_rng.cpp @@ -0,0 +1,293 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "graphapi_gtest_common.hpp" + +namespace { + +using miopen::graphapi::ValidatedValue; +using miopen::graphapi::ValidatedVector; +using DescriptorTuple = std::tuple, + double, + double, + ValidatedValue>; + +using miopen::graphapi::Rng; +using miopen::graphapi::RngBuilder; + +} // namespace + +class GraphApiRngBuilder : public testing::TestWithParam +{ +protected: + bool mAttrsValid; + miopenRngDistribution_t distribution; + double mNormalMean; + ValidatedValue mNormalStdev; + double mUniformMin; + double mUniformMax; + ValidatedValue mBernoulliProb; + + void SetUp() override + { + std::tie(mAttrsValid, + distribution, + mNormalMean, + mNormalStdev, + mUniformMin, + mUniformMax, + mBernoulliProb) = GetParam(); + } +}; + +TEST_P(GraphApiRngBuilder, ValidateAttributes) +{ + if(mAttrsValid && mNormalStdev.valid && mBernoulliProb.valid) + { + EXPECT_NO_THROW({ + RngBuilder() + .setDistribution(distribution) + .setNormalMean(mNormalMean) + .setNormalStdev(mNormalStdev.value) + .setUniformMin(mUniformMin) + .setUniformMax(mUniformMax) + .setBernoulliProb(mBernoulliProb.value) + .build(); + }) << "Builder failed on valid attributes"; + } + else + { + EXPECT_ANY_THROW({ + RngBuilder() + .setDistribution(distribution) + .setNormalMean(mNormalMean) + .setNormalStdev(mNormalStdev.value) + .setUniformMin(mUniformMin) + .setUniformMax(mUniformMax) + .setBernoulliProb(mBernoulliProb.value) + .build(); + }) << "Buider failed to detect invalid attributes"; + } + if(mNormalStdev.valid) + { + EXPECT_NO_THROW({ RngBuilder().setNormalStdev(mNormalStdev.value); }) + << "RngBuilder::setNormalStdev(double) failed on valid attribute"; + } + else + { + EXPECT_ANY_THROW({ RngBuilder().setNormalStdev(mNormalStdev.value); }) + << "RngBuilder::setNormalStdev(double) failed on invalid attribute"; + } + if(mBernoulliProb.valid) + { + EXPECT_NO_THROW({ RngBuilder().setBernoulliProb(mBernoulliProb.value); }) + << "RngBuilder::setBernoulliProb(double) failed on valid attribute"; + } + else + { + EXPECT_ANY_THROW({ RngBuilder().setBernoulliProb(mBernoulliProb.value); }) + << "RngBuilder::setBernoulliProb(double) failed on invalid attribute"; + } +} + +namespace { + +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +} // namespace + +class GraphApiRng : public testing::TestWithParam +{ +private: + // Pointers to these are used in mExecute object below + GTestDescriptorSingleValueAttribute mDistribution; + GTestDescriptorSingleValueAttribute mNormalMean; + GTestDescriptorSingleValueAttribute mNormalStdev; + GTestDescriptorSingleValueAttribute mUniformMin; + GTestDescriptorSingleValueAttribute mUniformMax; + GTestDescriptorSingleValueAttribute mBernoulliProb; + +protected: + GTestGraphApiExecute mExecute; + + void SetUp() override + { + auto [valid, distribution, normalMean, normalStdev, uniformMin, uniformMax, bernoulliProb] = + GetParam(); + + mDistribution = {true, + "MIOPEN_ATTR_RNG_DISTRIBUTION", + MIOPEN_ATTR_RNG_DISTRIBUTION, + MIOPEN_TYPE_RNG_DISTRIBUTION, + MIOPEN_TYPE_CHAR, + 2, + distribution}; + + mNormalMean = {true, + "MIOPEN_ATTR_RNG_NORMAL_DIST_MEAN", + MIOPEN_ATTR_RNG_NORMAL_DIST_MEAN, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 2, + normalMean}; + + mNormalStdev = {normalStdev.valid, + "MIOPEN_ATTR_RNG_NORMAL_DIST_STANDARD_DEVIATION", + MIOPEN_ATTR_RNG_NORMAL_DIST_STANDARD_DEVIATION, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 2, + normalStdev.value}; + + mUniformMin = {true, + "MIOPEN_ATTR_RNG_UNIFORM_DIST_MINIMUM", + MIOPEN_ATTR_RNG_UNIFORM_DIST_MINIMUM, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 2, + uniformMin}; + + mUniformMax = {true, + "MIOPEN_ATTR_RNG_UNIFORM_DIST_MAXIMUM", + MIOPEN_ATTR_RNG_UNIFORM_DIST_MAXIMUM, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 2, + uniformMax}; + + mBernoulliProb = {bernoulliProb.valid, + "MIOPEN_ATTR_RNG_BERNOULLI_DIST_PROBABILITY", + MIOPEN_ATTR_RNG_BERNOULLI_DIST_PROBABILITY, + MIOPEN_TYPE_DOUBLE, + MIOPEN_TYPE_CHAR, + 2, + bernoulliProb.value}; + + mExecute.descriptor.textName = "MIOPEN_BACKEND_RNG_DESCRIPTOR"; + mExecute.descriptor.type = MIOPEN_BACKEND_RNG_DESCRIPTOR; + mExecute.descriptor.attrsValid = valid; + + mExecute.descriptor.attributes = {&mDistribution, + &mNormalMean, + &mNormalStdev, + &mUniformMin, + &mUniformMax, + &mBernoulliProb}; + } +}; + +TEST_P(GraphApiRng, CFunctions) { mExecute(); } + +static auto validAttributesNormal = + testing::Combine(testing::Values(true), + testing::Values(MIOPEN_RNG_DISTRIBUTION_NORMAL), + testing::Values(0.0), + testing::Values(ValidatedValue{true, 0.5}), + testing::Values(0.0, 1.0), + testing::Values(0.0, 1.0), + testing::Values(ValidatedValue{true, 0.5}, + ValidatedValue{false, -0.5}, + ValidatedValue{false, 1.5})); + +static auto validAttributesUniform = testing::Combine( + testing::Values(true), + testing::Values(MIOPEN_RNG_DISTRIBUTION_UNIFORM), + testing::Values(0.0), + testing::Values(ValidatedValue{true, 0.5}, ValidatedValue{false, -0.5}), + testing::Values(0.0), + testing::Values(1.0), + testing::Values(ValidatedValue{true, 0.5}, + ValidatedValue{false, -0.5}, + ValidatedValue{false, 1.5})); + +static auto validAttributesBernoulli = testing::Combine( + testing::Values(true), + testing::Values(MIOPEN_RNG_DISTRIBUTION_BERNOULLI), + testing::Values(0.0), + testing::Values(ValidatedValue{true, 0.5}, ValidatedValue{false, -0.5}), + testing::Values(0.0, 1.0), + testing::Values(0.0, 1.0), + testing::Values(ValidatedValue{true, 0.5})); + +static auto invalidAttributesNormal = + testing::Combine(testing::Values(false), + testing::Values(MIOPEN_RNG_DISTRIBUTION_NORMAL), + testing::Values(0.0), + testing::Values(ValidatedValue{false, -0.5}), + testing::Values(0.0, 1.0), + testing::Values(0.0, 1.0), + testing::Values(ValidatedValue{true, 0.5}, + ValidatedValue{false, -0.5}, + ValidatedValue{false, 1.5})); + +static auto invalidAttributesUniform = testing::Combine( + testing::Values(false), + testing::Values(MIOPEN_RNG_DISTRIBUTION_UNIFORM), + testing::Values(0.0), + testing::Values(ValidatedValue{true, 0.5}, ValidatedValue{false, -0.5}), + testing::Values(1.0), + testing::Values(0.0), + testing::Values(ValidatedValue{true, 0.5}, + ValidatedValue{false, -0.5}, + ValidatedValue{false, 1.5})); + +static auto invalidAttributesBernoulli = testing::Combine( + testing::Values(false), + testing::Values(MIOPEN_RNG_DISTRIBUTION_BERNOULLI), + testing::Values(0.0), + testing::Values(ValidatedValue{true, 0.5}, ValidatedValue{false, -0.5}), + testing::Values(0.0, 1.0), + testing::Values(0.0, 1.0), + testing::Values(ValidatedValue{false, -0.5}, ValidatedValue{false, 1.5})); + +INSTANTIATE_TEST_SUITE_P(ValidAttributesNormal, GraphApiRngBuilder, validAttributesNormal); +INSTANTIATE_TEST_SUITE_P(ValidAttributesUniform, GraphApiRngBuilder, validAttributesUniform); +INSTANTIATE_TEST_SUITE_P(ValidAttributesBernoulli, GraphApiRngBuilder, validAttributesBernoulli); + +INSTANTIATE_TEST_SUITE_P(InvalidAttributesNormal, GraphApiRngBuilder, invalidAttributesNormal); +INSTANTIATE_TEST_SUITE_P(InvalidAttributesUniform, GraphApiRngBuilder, invalidAttributesUniform); +INSTANTIATE_TEST_SUITE_P(InvalidAttributesBernoulli, + GraphApiRngBuilder, + invalidAttributesBernoulli); + +INSTANTIATE_TEST_SUITE_P(ValidAttributesNormal, GraphApiRng, validAttributesNormal); +INSTANTIATE_TEST_SUITE_P(ValidAttributesUniform, GraphApiRng, validAttributesUniform); +INSTANTIATE_TEST_SUITE_P(ValidAttributesBernoulli, GraphApiRng, validAttributesBernoulli); + +INSTANTIATE_TEST_SUITE_P(InvalidAttributesNormal, GraphApiRng, invalidAttributesNormal); +INSTANTIATE_TEST_SUITE_P(InvalidAttributesUniform, GraphApiRng, invalidAttributesUniform); +INSTANTIATE_TEST_SUITE_P(InvalidAttributesBernoulli, GraphApiRng, invalidAttributesBernoulli); diff --git a/test/gtest/graphapi_tensor.cpp b/test/gtest/graphapi_tensor.cpp new file mode 100644 index 0000000000..749ebee1fd --- /dev/null +++ b/test/gtest/graphapi_tensor.cpp @@ -0,0 +1,229 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 + +TEST(BackendApi, Tensor) +{ + miopenBackendDescriptor_t tensorDescriptor; + + miopenStatus_t status = + miopenBackendCreateDescriptor(MIOPEN_BACKEND_TENSOR_DESCRIPTOR, &tensorDescriptor); + ASSERT_TRUE(status == miopenStatusSuccess); + + status = miopenBackendFinalize(tensorDescriptor); + EXPECT_FALSE(status == miopenStatusSuccess); + + int64_t theId = 1; + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_UNIQUE_ID, MIOPEN_TYPE_BOOLEAN, 1, &theId); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_UNIQUE_ID, MIOPEN_TYPE_INT64, 2, &theId); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_UNIQUE_ID, MIOPEN_TYPE_INT64, 1, &theId); + EXPECT_TRUE(status == miopenStatusSuccess); + + miopenDataType_t theDataType = miopenFloat; + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_DATA_TYPE, MIOPEN_TYPE_CHAR, 1, &theDataType); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_DATA_TYPE, MIOPEN_TYPE_DATA_TYPE, 2, &theDataType); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_DATA_TYPE, MIOPEN_TYPE_DATA_TYPE, 1, &theDataType); + EXPECT_TRUE(status == miopenStatusSuccess); + + int64_t dimensions[4] = {4, 1, 16, 16}; + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_DIMENSIONS, MIOPEN_TYPE_INT32, 4, dimensions); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_DIMENSIONS, MIOPEN_TYPE_INT64, 0, dimensions); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_DIMENSIONS, MIOPEN_TYPE_INT64, 4, dimensions); + EXPECT_TRUE(status == miopenStatusSuccess); + + int64_t strides[4] = {1 * 16 * 16, 16 * 16, 16, 1}; + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_STRIDES, MIOPEN_TYPE_INT32, 4, strides); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_STRIDES, MIOPEN_TYPE_INT64, 0, strides); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_STRIDES, MIOPEN_TYPE_INT64, 4, strides); + EXPECT_TRUE(status == miopenStatusSuccess); + + bool isVirtual = true; + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_IS_VIRTUAL, MIOPEN_TYPE_CHAR, 1, &isVirtual); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_IS_VIRTUAL, MIOPEN_TYPE_BOOLEAN, 2, &isVirtual); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendSetAttribute( + tensorDescriptor, MIOPEN_ATTR_TENSOR_IS_VIRTUAL, MIOPEN_TYPE_BOOLEAN, 1, &isVirtual); + EXPECT_TRUE(status == miopenStatusSuccess); + + int64_t elementCount = 0; + + int64_t retrievedId = 0; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_UNIQUE_ID, + MIOPEN_TYPE_INT64, + 1, + &elementCount, + &retrievedId); + EXPECT_FALSE(status == miopenStatusSuccess); + + status = miopenBackendFinalize(tensorDescriptor); + ASSERT_TRUE(status == miopenStatusSuccess); + + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_UNIQUE_ID, + MIOPEN_TYPE_BOOLEAN, + 1, + &elementCount, + &retrievedId); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_UNIQUE_ID, + MIOPEN_TYPE_INT64, + 2, + &elementCount, + &retrievedId); + EXPECT_FALSE(status == miopenStatusSuccess); + elementCount = 0; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_UNIQUE_ID, + MIOPEN_TYPE_INT64, + 1, + &elementCount, + &retrievedId); + ASSERT_TRUE(status == miopenStatusSuccess); + EXPECT_EQ(elementCount, 1); + EXPECT_TRUE(retrievedId == theId); + + miopenDataType_t retrievedDataType = miopenHalf; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_DATA_TYPE, + MIOPEN_TYPE_CHAR, + 1, + &elementCount, + &retrievedDataType); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_DATA_TYPE, + MIOPEN_TYPE_DATA_TYPE, + 2, + &elementCount, + &retrievedDataType); + EXPECT_FALSE(status == miopenStatusSuccess); + elementCount = 0; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_DATA_TYPE, + MIOPEN_TYPE_DATA_TYPE, + 1, + &elementCount, + &retrievedDataType); + ASSERT_TRUE(status == miopenStatusSuccess); + EXPECT_EQ(elementCount, 1); + EXPECT_TRUE(retrievedDataType == theDataType); + + int64_t retrievedDimensions[4] = {0, 0, 0, 0}; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_DIMENSIONS, + MIOPEN_TYPE_INT32, + 4, + &elementCount, + retrievedDimensions); + EXPECT_FALSE(status == miopenStatusSuccess); + elementCount = 0; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_DIMENSIONS, + MIOPEN_TYPE_INT64, + 4, + &elementCount, + retrievedDimensions); + ASSERT_TRUE(status == miopenStatusSuccess); + EXPECT_EQ(elementCount, 4); + for(int i = 0; i < elementCount; ++i) + { + EXPECT_TRUE(retrievedDimensions[i] == dimensions[i]); + } + + int64_t retrievedStrides[4] = {0, 0, 0, 0}; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_STRIDES, + MIOPEN_TYPE_INT32, + 4, + &elementCount, + retrievedStrides); + EXPECT_FALSE(status == miopenStatusSuccess); + elementCount = 0; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_STRIDES, + MIOPEN_TYPE_INT64, + 4, + &elementCount, + retrievedStrides); + ASSERT_TRUE(status == miopenStatusSuccess); + EXPECT_EQ(elementCount, 4); + for(int i = 0; i < elementCount; ++i) + { + EXPECT_TRUE(retrievedStrides[i] == strides[i]); + } + + elementCount = 0; + bool retrievedIsVirtual = false; + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_IS_VIRTUAL, + MIOPEN_TYPE_CHAR, + 1, + &elementCount, + &retrievedIsVirtual); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_IS_VIRTUAL, + MIOPEN_TYPE_BOOLEAN, + 2, + &elementCount, + &retrievedIsVirtual); + EXPECT_FALSE(status == miopenStatusSuccess); + status = miopenBackendGetAttribute(tensorDescriptor, + MIOPEN_ATTR_TENSOR_IS_VIRTUAL, + MIOPEN_TYPE_BOOLEAN, + 1, + &elementCount, + &retrievedIsVirtual); + ASSERT_TRUE(status == miopenStatusSuccess); + EXPECT_EQ(elementCount, 1); + EXPECT_TRUE(retrievedIsVirtual == isVirtual); +} diff --git a/test/gtest/graphapi_variant_pack.cpp b/test/gtest/graphapi_variant_pack.cpp new file mode 100644 index 0000000000..a86f08020f --- /dev/null +++ b/test/gtest/graphapi_variant_pack.cpp @@ -0,0 +1,457 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 +#include +#include + +#include + +#include "graphapi_gtest_common.hpp" + +namespace { + +using miopen::graphapi::ValidatedValue; +using miopen::graphapi::ValidatedVector; +using GraphApiVariantPackTuple = + std::tuple, ValidatedVector, ValidatedValue>; + +} // namespace + +class GraphApiVariantPackBuilder : public testing::TestWithParam +{ +protected: + bool mAttrsValid; + ValidatedVector mTensorIds; + ValidatedVector mDataPointers; + ValidatedValue mWorkspace; + + void SetUp() override + { + std::tie(mAttrsValid, mTensorIds, mDataPointers, mWorkspace) = GetParam(); + } + miopen::graphapi::VariantPack buildByLValue() + { + miopen::graphapi::VariantPackBuilder builder; + return builder.setTensorIds(mTensorIds.values) + .setDataPointers(mDataPointers.values) + .setWorkspace(mWorkspace.value) + .build(); + } + miopen::graphapi::VariantPack buildByRValue() + { + return miopen::graphapi::VariantPackBuilder() + .setTensorIds(mTensorIds.values) + .setDataPointers(mDataPointers.values) + .setWorkspace(mWorkspace.value) + .build(); + } + void setIdsByRValue(bool passAttrByRValue) + { + if(passAttrByRValue) + { + auto attr = mTensorIds.values; + miopen::graphapi::VariantPackBuilder().setTensorIds(std::move(attr)); + } + else + { + miopen::graphapi::VariantPackBuilder().setTensorIds(mTensorIds.values); + } + } + void setIdsByLValue(bool passAttrByRValue) + { + miopen::graphapi::VariantPackBuilder builder; + if(passAttrByRValue) + { + auto attr = mTensorIds.values; + builder.setTensorIds(std::move(attr)); + } + else + { + builder.setTensorIds(mTensorIds.values); + } + } + void setPointersByRValue(bool passAttrByRValue) + { + if(passAttrByRValue) + { + auto attr = mDataPointers.values; + miopen::graphapi::VariantPackBuilder().setDataPointers(std::move(attr)); + } + else + { + miopen::graphapi::VariantPackBuilder().setDataPointers(mDataPointers.values); + } + } + void setPointersByLValue(bool passAttrByRValue) + { + miopen::graphapi::VariantPackBuilder builder; + if(passAttrByRValue) + { + auto attr = mDataPointers.values; + builder.setDataPointers(std::move(attr)); + } + else + { + builder.setDataPointers(mDataPointers.values); + } + } + void setWorkspace(bool byRValue) + { + if(byRValue) + { + miopen::graphapi::VariantPackBuilder().setWorkspace(mWorkspace.value); + } + else + { + miopen::graphapi::VariantPackBuilder builder; + builder.setWorkspace(mWorkspace.value); + } + } +}; + +TEST_P(GraphApiVariantPackBuilder, ValidateAttributes) +{ + if(mAttrsValid) + { + EXPECT_NO_THROW({ buildByRValue(); }) << "R-value builder failed on valid attributes"; + EXPECT_NO_THROW({ buildByLValue(); }) << "L-value builder failed on valid attribures"; + } + else + { + EXPECT_ANY_THROW({ buildByRValue(); }) + << "R-value builder failed to detect invalid attributes"; + EXPECT_ANY_THROW({ buildByLValue(); }) + << "L-value builder failed to detect invalid attributes"; + } + if(mTensorIds.valid) + { + EXPECT_NO_THROW({ setIdsByRValue(false); }) + << "VariantPackBuilder::setTensorIds(const std::vector&)&& failed on valid " + "attribute"; + EXPECT_NO_THROW({ setIdsByRValue(true); }) + << "VariantPackBuilder::setTensorIds(std::vector&&)&& failed on valid " + "attribute"; + EXPECT_NO_THROW({ setIdsByLValue(false); }) + << "VariantPackBuilder::setTensorIds(const std::vector&)& failed on valid " + "attribute"; + EXPECT_NO_THROW({ + setIdsByLValue(true); + }) << "VariantPackBuilder::setTensorIds(std::vector&&)& failed on valid attribute"; + } + else + { + EXPECT_ANY_THROW({ setIdsByRValue(false); }) + << "VariantPackBuilder::setTensorIds(const std::vector&)&& failed on invalid " + "attribute"; + EXPECT_ANY_THROW({ setIdsByRValue(true); }) + << "VariantPackBuilder::setTensorIds(std::vector&&)&& failed on invalid " + "attribute"; + EXPECT_ANY_THROW({ setIdsByLValue(false); }) + << "VariantPackBuilder::setTensorIds(const std::vector&)& failed on invalid " + "attribute"; + EXPECT_ANY_THROW({ setIdsByLValue(true); }) + << "VariantPackBuilder::setTensorIds(std::vector&&)& failed on invalid " + "attribute"; + } + if(mDataPointers.valid) + { + EXPECT_NO_THROW({ setPointersByRValue(false); }) + << "VariantPackBuilder::setDataPointers(const std::vector&)&& failed on valid " + "attribute"; + EXPECT_NO_THROW({ setPointersByRValue(true); }) + << "VariantPackBuilder::setDataPointers(std::vector&&)&& failed on valid " + "attribute"; + EXPECT_NO_THROW({ setPointersByLValue(false); }) + << "VariantPackBuilder::setDataPointers(const std::vector&)& failed on valid " + "attribute"; + EXPECT_NO_THROW({ setPointersByLValue(true); }) + << "VariantPackBuilder::setDataPointers(std::vector&&)& failed on valid " + "attribute"; + } + else + { + EXPECT_ANY_THROW({ setPointersByRValue(false); }) + << "VariantPackBuilder::setDataPointers(const std::vector&)&& failed on invalid " + "attribute"; + EXPECT_ANY_THROW({ setPointersByRValue(true); }) + << "VariantPackBuilder::setDataPointers(std::vector&&)&& failed on invalid " + "attribute"; + EXPECT_ANY_THROW({ setPointersByLValue(false); }) + << "VariantPackBuilder::setDataPointers(const std::vector&)& failed on invalid " + "attribute"; + EXPECT_ANY_THROW({ setPointersByLValue(true); }) + << "VariantPackBuilder::setDataPointers(std::vector&&)& failed on invalid " + "attribute"; + } + if(mWorkspace.valid) + { + EXPECT_NO_THROW({ setWorkspace(true); }) + << "VariantPackBuilder::setWorkspace(void*)&& failed on valid attribute"; + EXPECT_NO_THROW({ setWorkspace(false); }) + << "VariantPackBuilder::setWorkspace(void*)& failed on valid attribute"; + } + else + { + EXPECT_ANY_THROW({ setWorkspace(true); }) + << "VariantPackBuilder::setWorkspace(void*)&& failed on invalid attribute"; + EXPECT_ANY_THROW({ setWorkspace(false); }) + << "VariantPackBuilder::setWorkspace(void*)& failed on invalid attribute"; + } +} + +TEST_P(GraphApiVariantPackBuilder, MissingSetter) +{ + EXPECT_ANY_THROW({ + miopen::graphapi::VariantPackBuilder() + .setDataPointers(mDataPointers.values) + .setWorkspace(mWorkspace.value) + .build(); + }) << "R-value builder validated attributes despite missing setTensorIds() call"; + EXPECT_ANY_THROW({ + miopen::graphapi::VariantPackBuilder() + .setTensorIds(mTensorIds.values) + .setWorkspace(mWorkspace.value) + .build(); + }) << "R-value builder validated attributes despite missing setDataPointers() call"; + EXPECT_ANY_THROW({ + miopen::graphapi::VariantPackBuilder() + .setTensorIds(mTensorIds.values) + .setDataPointers(mDataPointers.values) + .build(); + }) << "R-value builder validated attributes despite missing setWorkspace() call"; + EXPECT_ANY_THROW({ + miopen::graphapi::VariantPackBuilder builder; + builder.setDataPointers(mDataPointers.values).setWorkspace(mWorkspace.value).build(); + }) << "L-value builder validated attributes despite missing setTensorIds() call"; + EXPECT_ANY_THROW({ + miopen::graphapi::VariantPackBuilder builder; + builder.setTensorIds(mTensorIds.values).setWorkspace(mWorkspace.value).build(); + }) << "L-value builder validated attributes despite missing setDataPointers() call"; + EXPECT_ANY_THROW({ + miopen::graphapi::VariantPackBuilder builder; + builder.setTensorIds(mTensorIds.values).setDataPointers(mDataPointers.values).build(); + }) << "L-value builder validated attributes despite missing setWorkspace() call"; +} + +TEST_P(GraphApiVariantPackBuilder, RetrieveAttributes) +{ + miopen::graphapi::VariantPack vPack; + if(mAttrsValid) + { + ASSERT_NO_THROW({ vPack = buildByRValue(); }) << "Builder failed on valid attributes"; + } + else + { + ASSERT_ANY_THROW({ vPack = buildByRValue(); }) + << "Builder failed to detect invalid attributes"; + return; + } + + auto idsAndPointersCorrect = + std::inner_product(mTensorIds.values.cbegin(), + mTensorIds.values.cend(), + mDataPointers.values.cbegin(), + true, + std::logical_and<>(), + [&vPack](auto id, auto ptr) { return vPack.getDataPointer(id) == ptr; }); + EXPECT_TRUE(idsAndPointersCorrect) + << "Tensor ids or data pointers are set or retrieved incorrectly"; + EXPECT_EQ(vPack.getWorkspace(), mWorkspace.value) + << "Workspace is set or retrieved incorrectly"; +} + +namespace { + +using miopen::graphapi::GTestDescriptor; +using miopen::graphapi::GTestDescriptorAttribute; +using miopen::graphapi::GTestDescriptorSingleValueAttribute; +using miopen::graphapi::GTestDescriptorVectorAttribute; +using miopen::graphapi::GTestGraphApiExecute; + +class TensorIds : public GTestDescriptorVectorAttribute +{ +public: + TensorIds() = default; + TensorIds(const ValidatedVector& vv) + : GTestDescriptorVectorAttribute(vv.valid, + "MIOPEN_ATTR_VARIANT_PACK_UNIQUE_IDS", + MIOPEN_ATTR_VARIANT_PACK_UNIQUE_IDS, + MIOPEN_TYPE_INT64, + MIOPEN_TYPE_CHAR, + -1, + vv.values) + { + } +}; + +class DataPointers : public GTestDescriptorVectorAttribute +{ +public: + DataPointers() = default; + DataPointers(const ValidatedVector& vv) + : GTestDescriptorVectorAttribute(vv.valid, + "MIOPEN_ATTR_VARIANT_PACK_DATA_POINTERS", + MIOPEN_ATTR_VARIANT_PACK_DATA_POINTERS, + MIOPEN_TYPE_VOID_PTR, + MIOPEN_TYPE_CHAR, + -1, + vv.values) + { + } +}; + +class Workspace : public GTestDescriptorSingleValueAttribute +{ +public: + Workspace() = default; + Workspace(const ValidatedValue& vv) + : GTestDescriptorSingleValueAttribute(vv.valid, + "MIOPEN_ATTR_VARIANT_PACK_WORKSPACE", + MIOPEN_ATTR_VARIANT_PACK_WORKSPACE, + MIOPEN_TYPE_VOID_PTR, + MIOPEN_TYPE_CHAR, + 2, + vv.value) + { + } +}; + +} // namespace + +class GraphApiVariantPack : public testing::TestWithParam +{ +private: + // Pointers to these are used in mExecute object below + TensorIds mTensorIds; + DataPointers mDataPointers; + Workspace mWorkspace; + +protected: + GTestGraphApiExecute mExecute; + + void SetUp() override + { + bool valid = false; + std::tie(valid, mTensorIds, mDataPointers, mWorkspace) = GetParam(); + mExecute.descriptor = {"MIOPEN_BACKEND_VARIANT_PACK_DESCRIPTOR", + MIOPEN_BACKEND_VARIANT_PACK_DESCRIPTOR, + valid, + {&mTensorIds, &mDataPointers, &mWorkspace}}; + } +}; + +TEST_P(GraphApiVariantPack, CFunctions) { mExecute(); } + +static char mem[10][256]; + +static auto ValidAttributesTestCases = + testing::Values(GraphApiVariantPackTuple{true, {true, {}}, {true, {}}, {true, mem[9]}}, + GraphApiVariantPackTuple{ + true, {true, {1, 2, 3}}, {true, {mem[0], mem[1], mem[2]}}, {true, mem[9]}}, + GraphApiVariantPackTuple{true, + {true, {1, 2, 3, 4, 5}}, + {true, {mem[0], mem[1], mem[2], mem[3], mem[4]}}, + {true, mem[9]}}); + +static auto InvalidIdsTestCases = testing::Combine( + testing::Values(false), + testing::Values(ValidatedVector{false, {1, 2, 1, 4, 5}}, + ValidatedVector{false, {1, 2, 3, 2, 5}}, + ValidatedVector{false, {1, 5, 3, 4, 5}}), + testing::Values(ValidatedVector{true, {mem[0], mem[1], mem[2], mem[3], mem[4]}}, + ValidatedVector{true, {mem[0], mem[1], mem[2]}}, + ValidatedVector{true, {}}), + testing::Values(ValidatedValue{true, mem[9]})); + +static auto InvalidPointersTestCases = testing::Combine( + testing::Values(false), + testing::Values(ValidatedVector{true, {1, 2, 3, 4, 5}}, + ValidatedVector{true, {1, 2, 3}}, + ValidatedVector{true, {}}), + testing::Values(ValidatedVector{false, {nullptr, mem[1], mem[2], mem[3], mem[4]}}, + ValidatedVector{false, {mem[0], nullptr, mem[2], mem[3], mem[4]}}, + ValidatedVector{false, {mem[0], mem[1], nullptr, mem[3], mem[4]}}, + ValidatedVector{false, {mem[0], mem[1], mem[2], nullptr, mem[4]}}, + ValidatedVector{false, {mem[0], mem[1], mem[2], mem[3], nullptr}}, + ValidatedVector{false, {mem[0], mem[1], mem[2], mem[0], mem[4]}}, + ValidatedVector{false, {mem[0], mem[4], mem[2], mem[3], mem[4]}}, + ValidatedVector{false, {mem[0], mem[1], mem[2], mem[2], mem[4]}}), + testing::Values(ValidatedValue{false, nullptr}, + ValidatedValue{true, mem[2]}, + ValidatedValue{true, mem[9]})); + +static auto NullWorkspace = testing::Combine( + testing::Values(false), + testing::Values(ValidatedVector{true, {}}, + ValidatedVector{true, {1, 2, 3, 4, 5}}), + testing::Values(ValidatedVector{true, {}}, + ValidatedVector{true, {mem[0], mem[1], mem[2], mem[3], mem[4]}}), + testing::Values(ValidatedValue{false, nullptr})); + +static auto InvalidWorkspace = testing::Combine( + testing::Values(false), + testing::Values(ValidatedVector{true, {}}, + ValidatedVector{true, {1, 2, 3, 4, 5}}), + testing::Values(ValidatedVector{true, {mem[0], mem[1], mem[2], mem[3], mem[4]}}), + testing::Values(ValidatedValue{true, mem[0]}, + ValidatedValue{true, mem[1]}, + ValidatedValue{true, mem[2]}, + ValidatedValue{true, mem[3]}, + ValidatedValue{true, mem[4]})); + +static auto SizeMismatch = testing::Combine( + testing::Values(false), + testing::Values(ValidatedVector{true, {}}, + ValidatedVector{true, {1, 2, 3, 4, 5}}, + ValidatedVector{false, {5, 2, 3, 4, 5}}), + testing::Values(ValidatedVector{true, {mem[0]}}, + ValidatedVector{false, {nullptr}}, + ValidatedVector{true, {mem[0], mem[1], mem[2], mem[3]}}, + ValidatedVector{false, {mem[0], mem[3], mem[2], mem[3]}}), + testing::Values(ValidatedValue{true, mem[0]}, + ValidatedValue{true, mem[1]}, + ValidatedValue{true, mem[2]}, + ValidatedValue{true, mem[3]}, + ValidatedValue{true, mem[9]})); + +INSTANTIATE_TEST_SUITE_P(ValidAttributes, GraphApiVariantPackBuilder, ValidAttributesTestCases); +INSTANTIATE_TEST_SUITE_P(InvalidIds, GraphApiVariantPackBuilder, InvalidIdsTestCases); +INSTANTIATE_TEST_SUITE_P(InvalidPointers, GraphApiVariantPackBuilder, InvalidPointersTestCases); +INSTANTIATE_TEST_SUITE_P(NullWorkspace, GraphApiVariantPackBuilder, NullWorkspace); +INSTANTIATE_TEST_SUITE_P(InvalidWorkspace, GraphApiVariantPackBuilder, InvalidWorkspace); +INSTANTIATE_TEST_SUITE_P(SizeMismatch, GraphApiVariantPackBuilder, SizeMismatch); + +INSTANTIATE_TEST_SUITE_P(ValidAttributes, GraphApiVariantPack, ValidAttributesTestCases); +INSTANTIATE_TEST_SUITE_P(InvalidIds, GraphApiVariantPack, InvalidIdsTestCases); +INSTANTIATE_TEST_SUITE_P(InvalidPointers, GraphApiVariantPack, InvalidPointersTestCases); +INSTANTIATE_TEST_SUITE_P(NullWorkspace, GraphApiVariantPack, NullWorkspace); +INSTANTIATE_TEST_SUITE_P(InvalidWorkspace, GraphApiVariantPack, InvalidWorkspace); +INSTANTIATE_TEST_SUITE_P(SizeMismatch, GraphApiVariantPack, SizeMismatch); diff --git a/test/gtest/groupnorm.hpp b/test/gtest/groupnorm.hpp index 910c1b0118..33c4ed105f 100644 --- a/test/gtest/groupnorm.hpp +++ b/test/gtest/groupnorm.hpp @@ -145,15 +145,6 @@ std::vector GroupNormTestConfigs() {64, 1, 0, 0, 256, 1, 1e-5, MIOPEN_WEIGHT_BIAS}}; } -inline int32_t SetTensorLayout(miopen::TensorDescriptor& desc) -{ - const std::vector lens = desc.GetLengths(); - std::vector int32_t_lens(lens.begin(), lens.end()); - - // set the strides for the tensor - return SetTensorNd(&desc, int32_t_lens, desc.GetType()); -} - template struct GroupNormTest : public ::testing::TestWithParam { @@ -190,13 +181,6 @@ struct GroupNormTest : public ::testing::TestWithParam mean = tensor{mean_rstd_dim}; rstd = tensor{mean_rstd_dim}; - SetTensorLayout(weight.desc); - SetTensorLayout(bias.desc); - SetTensorLayout(input.desc); - SetTensorLayout(output.desc); - SetTensorLayout(mean.desc); - SetTensorLayout(rstd.desc); - std::fill(output.begin(), output.end(), std::numeric_limits::quiet_NaN()); std::fill(mean.begin(), mean.end(), std::numeric_limits::quiet_NaN()); std::fill(rstd.begin(), rstd.end(), std::numeric_limits::quiet_NaN()); diff --git a/test/gtest/kernel_tuning_net.cpp b/test/gtest/kernel_tuning_net.cpp index 89b78f80f5..9cf763d47e 100644 --- a/test/gtest/kernel_tuning_net.cpp +++ b/test/gtest/kernel_tuning_net.cpp @@ -7,31 +7,79 @@ struct KernelTuningNetTestCase : AIModelTestCase { std::string expected_config; + std::string arch; }; std::vector GetConvAsm1x1UTestCases() { - return {{{{512, 192, 56, 56, 288, 1, 1, 0, 0, 1, 1, 1, 1, miopenConvolution}, + return {{{{1, 512, 192, 288, {56, 56}, {1, 1}, {0, 0}, {1, 1}, {1, 1}}, miopen::conv::Direction::BackwardData, miopenFloat, miopenTensorNCHW}, - "1,16,1,64,2,2,1,4"}, - {{{256, 2048, 7, 7, 512, 1, 1, 0, 0, 1, 1, 1, 1, miopenConvolution}, + "1,16,1,64,2,2,1,4", + "gfx908"}, + {{{1, 256, 2048, 512, {7, 7}, {1, 1}, {0, 0}, {1, 1}, {1, 1}}, miopen::conv::Direction::Forward, miopenHalf, miopenTensorNCHW}, - "2,8,4,16,1,4,1,4"}}; + "2,8,4,16,1,4,1,4", + "gfx908"}}; } std::vector GetConvHipIgemmGroupFwdXdlopsTestCases() { return { - {{{128, 64, 209, 209, 128, 3, 3, 0, 0, 2, 2, 1, 1, miopenConvolution}, + {{{1, 128, 64, 128, {209, 209}, {3, 3}, {0, 0}, {2, 2}, {1, 1}}, miopen::conv::Direction::Forward, miopenFloat, miopenTensorNHWC}, "DeviceGroupedConvFwdMultipleABD_Xdl_CShuffle<256, 128, 128, 16, Default, 32, 32, 2, 2, " - "4, 4, 4, 1, 1>"}}; + "4, 4, 4, 1, 1>", + "gfx90a"}, + {{{48, 32, 48, 48, {14, 14}, {3, 3}, {1, 1}, {1, 1}, {1, 1}}, + miopen::conv::Direction::Forward, + miopenHalf, + miopenTensorNHWC}, + "DeviceGroupedConvFwdMultipleABD_Xdl_CShuffle<64, 64, 64, 32, Default, 32, 32, 2, 2, 1, " + "1, 1, 1, 1>", + "gfx942"}, + }; +} + +std::vector GetConvHipIgemmGroupBwdXdlopsTestCases() +{ + return {{{{32, 4, 256, 256, {59, 59}, {3, 3}, {1, 1}, {2, 2}, {1, 1}}, + miopen::conv::Direction::BackwardData, + miopenHalf, + miopenTensorNHWC}, + "DeviceGroupedConvBwdDataMultipleD_Xdl_CShuffle_v1<128, 128, 32, 32, 8, 8, Default, " + "32, 32, 2, 1, 8, 8, 1, 1>", + "gfx90a"}, + {{{64, 96, 64, 64, {224, 224}, {3, 3}, {1, 1}, {1, 1}, {1, 1}}, + miopen::conv::Direction::BackwardData, + miopenFloat, + miopenTensorNHWC}, + "DeviceGroupedConvBwdDataMultipleD_Xdl_CShuffle_v1<64, 64, 64, 32, 8, 8, Default, 32, " + "32, 2, 2, 1, 1, 1, 1>", + "gfx942"}}; +} + +std::vector GetConvHipIgemmGroupWrwXdlopsTestCases() +{ + return {{{{4, 2, 512, 512, {24, 36}, {3, 3}, {1, 1}, {1, 1}, {1, 1}}, + miopen::conv::Direction::BackwardWeights, + miopenFloat, + miopenTensorNHWC}, + "DeviceGroupedConvBwdWeight_Xdl_CShuffle<128, 128, 32, 4, Default, 4, 2, 1, 4, 4, 4, " + "1, 1, 1, 4>", + "gfx942"}, + {{{1, 16, 128, 256, {27, 27}, {3, 3}, {0, 0}, {1, 2}, {1, 1}}, + miopen::conv::Direction::BackwardWeights, + miopenHalf, + miopenTensorNHWC}, + "DeviceGroupedConvBwdWeight_Xdl_CShuffle<64, 64, 32, 4, Default, 8, 2, 1, 8, 4, 8, 2, " + "1, 1, 8>", + "gfx90a"}}; } struct KernelTuningNetTest : public ::testing::TestWithParam @@ -60,22 +108,28 @@ struct KernelTuningNetTest : public ::testing::TestWithParam -void TestParameterPredictionModel(miopen::conv::ProblemDescription problem, std::string expected) +void TestParameterPredictionModel(miopen::conv::ProblemDescription problem, + std::string expected, + std::string arch) { #if MIOPEN_ENABLE_AI_KERNEL_TUNING auto&& handle = get_handle(); miopen::ExecutionContext ctx; ctx.SetStream(&handle); T perf_config; + if(arch != ctx.GetStream().GetDeviceName()) + GTEST_SKIP(); if(!perf_config.IsModelApplicable(ctx, problem)) GTEST_SKIP(); perf_config.HeuristicInit(ctx, problem); @@ -85,6 +139,7 @@ void TestParameterPredictionModel(miopen::conv::ProblemDescription problem, std: #else std::ignore = problem; std::ignore = expected; + std::ignore = arch; GTEST_SKIP(); #endif } @@ -97,17 +152,42 @@ struct KernelTuningNetTestConvHipIgemmGroupFwdXdlops : KernelTuningNetTest { }; +struct KernelTuningNetTestConvHipIgemmGroupBwdXdlops : KernelTuningNetTest +{ +}; + +struct KernelTuningNetTestConvHipIgemmGroupWrwXdlops : KernelTuningNetTest +{ +}; + TEST_P(KernelTuningNetTestConvAsm1x1U, ConvAsm1x1UParameterPredictionModel) { - TestParameterPredictionModel(problem, - expected); + TestParameterPredictionModel( + problem, expected, arch); } TEST_P(KernelTuningNetTestConvHipIgemmGroupFwdXdlops, ConvHipIgemmGroupFwdXdlopsParameterPredictionModel) { TestParameterPredictionModel< - miopen::solver::conv::PerformanceConfigHipImplicitGemmGroupFwdXdlops>(problem, expected); + miopen::solver::conv::PerformanceConfigHipImplicitGemmGroupFwdXdlops>( + problem, expected, arch); +} + +TEST_P(KernelTuningNetTestConvHipIgemmGroupBwdXdlops, + ConvHipIgemmGroupBwdXdlopsParameterPredictionModel) +{ + TestParameterPredictionModel< + miopen::solver::conv::PerformanceConfigHipImplicitGemmGroupBwdXdlops>( + problem, expected, arch); +} + +TEST_P(KernelTuningNetTestConvHipIgemmGroupWrwXdlops, + ConvHipIgemmGroupWrwXdlopsParameterPredictionModel) +{ + TestParameterPredictionModel< + miopen::solver::conv::PerformanceConfigHipImplicitGemmGroupWrwXdlops>( + problem, expected, arch); } INSTANTIATE_TEST_SUITE_P(ConvAsm1x1UParameterPredictionModelTest, @@ -117,3 +197,11 @@ INSTANTIATE_TEST_SUITE_P(ConvAsm1x1UParameterPredictionModelTest, INSTANTIATE_TEST_SUITE_P(ConvHipIgemmGroupFwdXdlopsParameterPredictionModelTest, KernelTuningNetTestConvHipIgemmGroupFwdXdlops, testing::ValuesIn(GetConvHipIgemmGroupFwdXdlopsTestCases())); + +INSTANTIATE_TEST_SUITE_P(ConvHipIgemmGroupBwdXdlopsParameterPredictionModelTest, + KernelTuningNetTestConvHipIgemmGroupBwdXdlops, + testing::ValuesIn(GetConvHipIgemmGroupBwdXdlopsTestCases())); + +INSTANTIATE_TEST_SUITE_P(ConvHipIgemmGroupWrwXdlopsParameterPredictionModelTest, + KernelTuningNetTestConvHipIgemmGroupWrwXdlops, + testing::ValuesIn(GetConvHipIgemmGroupWrwXdlopsTestCases())); diff --git a/test/gtest/layernorm.hpp b/test/gtest/layernorm.hpp index bd3f1cd85e..454b826adb 100644 --- a/test/gtest/layernorm.hpp +++ b/test/gtest/layernorm.hpp @@ -151,15 +151,6 @@ std::vector LayerNormTestConfigs() // clang-format on } -static int32_t SetTensorLayout(miopen::TensorDescriptor& desc) -{ - const std::vector& lens = desc.GetLengths(); - std::vector int32_t_lens(lens.begin(), lens.end()); - - // set the strides for the tensor - return SetTensorNd(&desc, int32_t_lens, desc.GetType()); -} - template struct LayerNormTest : public ::testing::TestWithParam { @@ -190,15 +181,11 @@ struct LayerNormTest : public ::testing::TestWithParam auto gen_zero = [&](auto...) { return 0; }; weight = tensor{inner_dim}.generate(gen_one); bias = tensor{inner_dim}.generate(gen_zero); - SetTensorLayout(weight.desc); - SetTensorLayout(bias.desc); } else { weight = tensor{inner_dim}.generate(gen_value); bias = tensor{inner_dim}.generate(gen_value); - SetTensorLayout(weight.desc); - SetTensorLayout(bias.desc); } std::vector outer_dim; @@ -207,14 +194,9 @@ struct LayerNormTest : public ::testing::TestWithParam else outer_dim = {in_dim.begin(), in_dim.end() - (in_dim.size() - nomalized_dim)}; - SetTensorLayout(input.desc); - output = tensor{in_dim}; mean = tensor{outer_dim}; rstd = tensor{outer_dim}; - SetTensorLayout(output.desc); - SetTensorLayout(mean.desc); - SetTensorLayout(rstd.desc); std::fill(output.begin(), output.end(), std::numeric_limits::quiet_NaN()); std::fill(mean.begin(), mean.end(), std::numeric_limits::quiet_NaN()); std::fill(rstd.begin(), rstd.end(), std::numeric_limits::quiet_NaN()); diff --git a/test/gtest/layernorm_test.hpp b/test/gtest/layernorm_test.hpp deleted file mode 100644 index 740108a887..0000000000 --- a/test/gtest/layernorm_test.hpp +++ /dev/null @@ -1,247 +0,0 @@ -/******************************************************************************* - * - * MIT License - * - * Copyright (c) 2023 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 -#ifdef MIOPEN_BETA_API -#include -#include - -#include "tensor_holder.hpp" -#include "cpu_layernorm.hpp" -#include "get_handle.hpp" -#include "../driver/tensor_driver.hpp" -#include "verify.hpp" -#include - -struct LayerNormTestCase -{ - size_t N; - size_t C; - size_t D; - size_t H; - size_t W; - size_t nomalized_dim; - float eps; - miopenLayerNormMode_t ln_mode; - friend std::ostream& operator<<(std::ostream& os, const LayerNormTestCase& tc) - { - return os << " N:" << tc.N << " C:" << tc.C << " D:" << tc.D << " H:" << tc.H - << " W:" << tc.W << " dim:" << tc.nomalized_dim << " eps:" << tc.eps - << " LayerNorm_mode:" << tc.ln_mode; - } - - std::vector GetInput() { return {N, C, D, H, W}; } -}; - -std::vector LayerNormTestConfigs() -{ // n c h d w nomalized_dim eps ln_mode - // clang-format off - return { - { 32, 1, 32, 32, 32 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 32x32x32 based on VoxNet arch - { 32, 1, 14, 14, 14 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 14, 14, 14 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 12, 12, 12 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 6, 6, 6 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 256, 1, 32, 32, 32 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 32x32x32 based on VoxNet arch - { 256, 32, 14, 14, 14 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 256, 32, 12, 12, 12 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 256, 32, 6, 6, 6 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 512, 1, 32, 32, 32 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 32x32x32 based on VoxNet arch - { 512, 32, 14, 14, 14 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 512, 32, 12, 12, 12 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 512, 32, 6, 6, 6 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 2, 32, 57, 125 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // Hand-gesture recognition CVPR 2015 paper High Res Net Path - { 32, 32, 14, 25, 59 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 6, 10, 27 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 4, 6, 11 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 2, 2, 3 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 32, 28, 62 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // Hand-gesture recognition CVPR 2015 paper Low Res Net Path - { 32, 32, 14, 12, 29 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 6, 4, 12 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 32, 32, 4, 2, 2 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, - { 16, 32, 6, 50, 50 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // Multi-view 3D convnet - { 1, 3, 8, 240, 320 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 3D convet on video - { 1, 3, 16, 240, 320 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 3D convet on video - { 1, 3, 8, 128, 171 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 3D convet on video - { 1, 3, 16, 128, 171 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 3D convet on video - { 1, 3, 8, 112, 112 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE}, // 3D convet on video - { 1, 3, 16, 112, 112 ,4 , 1e-5, MIOPEN_ELEMENTWISE_AFFINE} // 3D convet on video - }; - // clang-format on -} - -inline int32_t SetTensorLayout(miopen::TensorDescriptor& desc) -{ - std::vector lens = desc.GetLengths(); - std::vector int32_t_lens(lens.begin(), lens.end()); - - // set the strides for the tensor - return SetTensorNd(&desc, int32_t_lens, desc.GetType()); -} - -template -struct LayerNormSolverTest : public ::testing::TestWithParam -{ -protected: - void SetUp() override - { - auto&& handle = get_handle(); - test_skipped = false; - layernorm_config = GetParam(); - std::mt19937 gen(0); - std::uniform_real_distribution<> d{-3, 3}; - auto gen_value = [&](auto...) { return d(gen); }; - - nomalized_dim = layernorm_config.nomalized_dim; - eps = layernorm_config.eps; - ln_mode = layernorm_config.ln_mode; - - auto in_dim = layernorm_config.GetInput(); - - input = tensor{in_dim}.generate(gen_value); - - if(ln_mode == MIOPEN_ELEMENTWISE_AFFINE) - { - std::vector inner_dim; - if(nomalized_dim == in_dim.size()) - inner_dim = {1}; - else - inner_dim = {in_dim.begin() + nomalized_dim, in_dim.end()}; - weight = tensor{inner_dim}.generate(gen_value); - bias = tensor{inner_dim}.generate(gen_value); - SetTensorLayout(weight.desc); - SetTensorLayout(bias.desc); - } - - std::vector outer_dim; - if(nomalized_dim == 0) - outer_dim = {1}; - else - outer_dim = {in_dim.begin(), in_dim.end() - (in_dim.size() - nomalized_dim)}; - - SetTensorLayout(input.desc); - - output = tensor{in_dim}; - mean = tensor{outer_dim}; - rstd = tensor{outer_dim}; - SetTensorLayout(output.desc); - SetTensorLayout(mean.desc); - SetTensorLayout(rstd.desc); - std::fill(output.begin(), output.end(), std::numeric_limits::quiet_NaN()); - std::fill(mean.begin(), mean.end(), std::numeric_limits::quiet_NaN()); - std::fill(rstd.begin(), rstd.end(), std::numeric_limits::quiet_NaN()); - - ref_output = tensor{in_dim}; - ref_mean = tensor{outer_dim}; - ref_rstd = tensor{outer_dim}; - std::fill(ref_output.begin(), ref_output.end(), std::numeric_limits::quiet_NaN()); - std::fill(ref_mean.begin(), ref_mean.end(), std::numeric_limits::quiet_NaN()); - std::fill(ref_rstd.begin(), ref_rstd.end(), std::numeric_limits::quiet_NaN()); - - input_dev = handle.Write(input.data); - weight_dev = handle.Write(weight.data); - bias_dev = handle.Write(bias.data); - output_dev = handle.Write(output.data); - mean_dev = handle.Write(mean.data); - rstd_dev = handle.Write(rstd.data); - } - void TearDown() override - { - if(test_skipped) - return; - - auto&& handle = get_handle(); - - cpu_layernorm_forward( - input, weight, bias, ref_output, ref_mean, ref_rstd, eps, nomalized_dim, ln_mode); - miopenStatus_t status; - - status = miopen::LayerNormForward(handle, - input.desc, - input_dev.get(), - weight.desc, - weight_dev.get(), - bias.desc, - bias_dev.get(), - output.desc, - output_dev.get(), - mean.desc, - mean_dev.get(), - rstd.desc, - rstd_dev.get(), - ln_mode, - eps, - nomalized_dim); - - EXPECT_EQ(status, miopenStatusSuccess); - - output.data = handle.Read(output_dev, output.data.size()); - mean.data = handle.Read(mean_dev, mean.data.size()); - rstd.data = handle.Read(rstd_dev, rstd.data.size()); - - double threshold = std::numeric_limits::epsilon(); - auto error = miopen::rms_range(ref_output, output); - - EXPECT_TRUE(miopen::range_distance(ref_output) == miopen::range_distance(output)); - EXPECT_TRUE(error < threshold * 1000) << "Error output beyond tolerance Error:" << error - << ", Thresholdx1000: " << threshold * 1000; - - error = miopen::rms_range(ref_mean, mean); - EXPECT_TRUE(miopen::range_distance(ref_mean) == miopen::range_distance(mean)); - EXPECT_TRUE(error < threshold) - << "Error mean beyond tolerance Error:" << error << ", Threshold: " << threshold; - - error = miopen::rms_range(ref_rstd, rstd); - EXPECT_TRUE(miopen::range_distance(ref_rstd) == miopen::range_distance(rstd)); - EXPECT_TRUE(error < threshold * 2000) << "Error rstd beyond tolerance Error:" << error - << ", Thresholdx2000: " << threshold * 2000; - } - LayerNormTestCase layernorm_config; - - tensor input; - tensor weight; - tensor bias; - tensor output; - tensor mean; - tensor rstd; - - tensor ref_output; - tensor ref_mean; - tensor ref_rstd; - - miopen::Allocator::ManageDataPtr input_dev; - miopen::Allocator::ManageDataPtr weight_dev; - miopen::Allocator::ManageDataPtr bias_dev; - miopen::Allocator::ManageDataPtr output_dev; - miopen::Allocator::ManageDataPtr mean_dev; - miopen::Allocator::ManageDataPtr rstd_dev; - - size_t nomalized_dim; - float eps; - miopenLayerNormMode_t ln_mode; - - bool test_skipped = false; -}; -#endif diff --git a/test/gtest/mha_find20.cpp b/test/gtest/mha_find20.cpp new file mode 100644 index 0000000000..d924e11102 --- /dev/null +++ b/test/gtest/mha_find20.cpp @@ -0,0 +1,610 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "test.hpp" +#include "get_handle.hpp" +#include "tensor_holder.hpp" +#include "verify.hpp" +#include "../workspace.hpp" + +#include +#include +#include + +#include + +#include + +#include + +#include + +using namespace miopen; + +struct TensorStruct +{ + TensorStruct(bool isFloat = true) : isFloatTensor(isFloat) {} + + bool isFloatTensor; + tensor tensorFloat; + + Allocator::ManageDataPtr gpuBuffer; + + // TODO Unsued for now + // tensor tensorUint64; +}; + +typedef std::unique_ptr TensorStructPtr; + +typedef std::map TensorStructMap; + +class MhaFind20Test +{ +public: + MhaFind20Test(bool forward) : problem(nullptr), isForward(forward) { Initialize(); } + + std::vector TestFindSolutions(Handle& handle) + { + std::cerr << "Testing miopenFindSolutions..." << std::endl; + + auto solutions = std::vector{}; + std::size_t found = 0; + + solutions.resize(16); + + EXPECT_EQUAL(miopenFindSolutions( + &handle, problem, nullptr, solutions.data(), &found, solutions.size()), + miopenStatusSuccess); + EXPECT_TRUE(found > 0); + + solutions.resize(found); + + std::cerr << "Finished testing miopenFindSolutions." << std::endl; + return solutions; + } + + void TestSolutionAttributes(const std::vector& solutions) + { + std::cerr << "Testing miopenGetSolution..." << std::endl; + + for(const auto& solution : solutions) + { + float time; + std::size_t workspace_size; + uint64_t solver_id; + + EXPECT_EQUAL(miopenGetSolutionTime(solution, &time), miopenStatusSuccess); + EXPECT_EQUAL(miopenGetSolutionWorkspaceSize(solution, &workspace_size), + miopenStatusSuccess); + EXPECT_EQUAL(miopenGetSolutionSolverId(solution, &solver_id), miopenStatusSuccess); + } + + std::cerr << "Finished testing miopenGetSolution." << std::endl; + } + + void TestRunSolutions(Handle& handle, const std::vector& solutions) + { + std::cerr << "Testing a solution..." << std::endl; + + const size_t numTensors = tensors.size(); + + auto arguments = std::make_unique(numTensors); + + std::vector descVector(numTensors); + + int i = 0; + for(const auto& it : tensors) + { + descVector[i] = &it.second->tensorFloat.desc; + + it.second->gpuBuffer = handle.Write(it.second->tensorFloat.data); + + arguments[i].id = it.first; + arguments[i].descriptor = &descVector[i]; + arguments[i].buffer = it.second->gpuBuffer.get(); + + ++i; + } + + std::vector output_ids; + + if(isForward) + { + output_ids = {miopenTensorMhaO, + miopenTensorMhaAmaxO, + miopenTensorMhaAmaxS, + miopenTensorMhaM, + miopenTensorMhaZInv}; + } + else + { + output_ids = {miopenTensorMhaDQ, + miopenTensorMhaDK, + miopenTensorMhaDV, + miopenTensorMhaAmaxDQ, + miopenTensorMhaAmaxDK, + miopenTensorMhaAmaxDV, + miopenTensorMhaAmaxDS}; + } + + std::size_t workspace_size = 0; + uint64_t solver_id; + + Workspace workspace; + + for(const auto& solution : solutions) + { + miopenGetSolutionWorkspaceSize(solution, &workspace_size); + miopenGetSolutionSolverId(solution, &solver_id); + + workspace.resize(workspace_size); + + std::cerr << "Run a solution." << std::endl; + EXPECT_EQUAL(miopenRunSolution(&handle, + solution, + numTensors, + arguments.get(), + workspace.ptr(), + workspace.size()), + miopenStatusSuccess); + + std::cerr << "Solution executed." << std::endl; + + TensorStructMap outputTensorResults; + + // reading results from find 2.0 call and preparing structures for non find 2.0 outputs + // to compare with + for(const auto& id : output_ids) + { + const TensorStructPtr& tensorStructPtr = tensors[id]; + tensorStructPtr->tensorFloat.data = handle.Read( + tensorStructPtr->gpuBuffer, tensorStructPtr->tensorFloat.data.size()); + + TensorStructPtr& ptr = outputTensorResults[id]; + + ptr = std::make_unique(); + ptr->tensorFloat = tensorStructPtr->tensorFloat; + ptr->gpuBuffer = handle.Write(ptr->tensorFloat.data); + } + + std::cerr << "Run via solver infrastructure directly." << std::endl; + if(isForward) + { + GetForwardResultsWithoutFind20(handle, outputTensorResults, workspace, solver_id); + } + else + { + GetBackwardResultsWithoutFind20(handle, outputTensorResults, workspace, solver_id); + } + std::cerr << "Run via solver infrastructure executed!" << std::endl; + + for(const auto& id : output_ids) + { + double error = miopen::rms_range(outputTensorResults[id]->tensorFloat.data, + tensors[id]->tensorFloat.data); + const double tolerance = 1e-3; + + EXPECT_TRUE(std::isfinite(error)) << "Tensor id: " << id; + EXPECT_LE(error, tolerance) << "Tensor id: " << id; + } + } + + std::cerr << "Finished testing solution functions." << std::endl; + } + + void Finalize() { EXPECT_EQUAL(miopenDestroyProblem(problem), miopenStatusSuccess); } + +private: + bool IsInt64TensorId(miopenTensorArgumentId_t id) const + { + return id == miopenTensorMhaDropoutSeed || id == miopenTensorMhaDropoutOffset; + } + + enum class GenerateType + { + DontGenerate, + Generate_1_Always, + Generate_0_Always, + GenerateRandom + }; + + void CreateTensor(miopenTensorArgumentId_t id, + GenerateType generateType = GenerateType::Generate_1_Always, + unsigned int n = 1, + unsigned int h = 1, + unsigned int s = 1, + unsigned int d = 1) + { + // TODO Unused for now + bool floatFlag = !IsInt64TensorId(id); + tensors[id] = std::make_unique(floatFlag); + + tensors[id]->tensorFloat = tensor{n, h, s, d}; + + switch(generateType) + { + case GenerateType::Generate_0_Always: + tensors[id]->tensorFloat.generate([](auto n_, auto h_, auto s_, auto d_) { return 0; }); + break; + + case GenerateType::Generate_1_Always: + tensors[id]->tensorFloat.generate([](auto n_, auto h_, auto s_, auto d_) { return 1; }); + break; + + case GenerateType::GenerateRandom: + tensors[id]->tensorFloat.generate(tensor_elem_gen_integer{17}); + break; + + default: break; + } + + EXPECT_EQUAL(miopenSetProblemTensorDescriptor(problem, id, &tensors[id]->tensorFloat.desc), + miopenStatusSuccess); + } + + void Initialize() + { + mha_descriptor.SetParams(scale); + + EXPECT_EQUAL(miopenCreateMhaProblem(&problem, + &mha_descriptor, + isForward ? miopenProblemDirectionForward + : miopenProblemDirectionBackward), + miopenStatusSuccess); + + CreateTensor( + miopenTensorMhaK, GenerateType::GenerateRandom, test_n, test_h, test_s, test_d); + + CreateTensor( + miopenTensorMhaV, GenerateType::GenerateRandom, test_n, test_h, test_s, test_d); + + CreateTensor(miopenTensorMhaDescaleK); + CreateTensor(miopenTensorMhaDescaleQ); + CreateTensor(miopenTensorMhaDescaleV); + CreateTensor(miopenTensorMhaDescaleS); + + CreateTensor(miopenTensorMhaScaleS); + + CreateTensor(miopenTensorMhaDropoutProbability, GenerateType::Generate_0_Always); + CreateTensor(miopenTensorMhaDropoutSeed, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaDropoutOffset, GenerateType::GenerateRandom); + + if(isForward) + { + CreateTensor( + miopenTensorMhaQ, GenerateType::GenerateRandom, test_n, test_h, test_s, test_d); + + CreateTensor(miopenTensorMhaScaleO); + + CreateTensor( + miopenTensorMhaO, GenerateType::DontGenerate, test_n, test_h, test_s, test_d); + CreateTensor(miopenTensorMhaAmaxO, GenerateType::DontGenerate); + CreateTensor(miopenTensorMhaAmaxS, GenerateType::DontGenerate); + CreateTensor(miopenTensorMhaM, GenerateType::DontGenerate, test_n, test_h, test_s, 1); + CreateTensor( + miopenTensorMhaZInv, GenerateType::DontGenerate, test_n, test_h, test_s, 1); + } + else + { + CreateTensor( + miopenTensorMhaQ, GenerateType::Generate_0_Always, test_n, test_h, test_s, test_d); + + CreateTensor( + miopenTensorMhaO, GenerateType::GenerateRandom, test_n, test_h, test_s, test_d); + + CreateTensor( + miopenTensorMhaDO, GenerateType::GenerateRandom, test_n, test_h, test_s, test_d); + + CreateTensor( + miopenTensorMhaM, GenerateType::Generate_0_Always, test_n, test_h, test_s, 1); + CreateTensor( + miopenTensorMhaZInv, GenerateType::Generate_1_Always, test_n, test_h, test_s, 1); + + CreateTensor(miopenTensorMhaDescaleO, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaDescaleDO, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaDescaleDS, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaScaleDS, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaScaleDQ, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaScaleDK, GenerateType::GenerateRandom); + CreateTensor(miopenTensorMhaScaleDV, GenerateType::GenerateRandom); + + CreateTensor( + miopenTensorMhaDQ, GenerateType::DontGenerate, test_n, test_h, test_s, test_d); + CreateTensor( + miopenTensorMhaDK, GenerateType::DontGenerate, test_n, test_h, test_s, test_d); + CreateTensor( + miopenTensorMhaDV, GenerateType::DontGenerate, test_n, test_h, test_s, test_d); + CreateTensor(miopenTensorMhaAmaxDQ, GenerateType::DontGenerate); + CreateTensor(miopenTensorMhaAmaxDK, GenerateType::DontGenerate); + CreateTensor(miopenTensorMhaAmaxDV, GenerateType::DontGenerate); + CreateTensor(miopenTensorMhaAmaxDS, GenerateType::DontGenerate); + } + } + + void GetForwardResultsWithoutFind20(Handle& handle, + TensorStructMap& outputResultsMap, + Workspace& workspace, + uint64_t solver_id) + { + const auto& mhaK = tensors[miopenTensorMhaK]; + const auto& mhaQ = tensors[miopenTensorMhaQ]; + const auto& mhaV = tensors[miopenTensorMhaV]; + + const auto& mhaDescaleK = tensors[miopenTensorMhaDescaleK]; + const auto& mhaDescaleQ = tensors[miopenTensorMhaDescaleQ]; + const auto& mhaDescaleV = tensors[miopenTensorMhaDescaleV]; + const auto& mhaDescaleS = tensors[miopenTensorMhaDescaleS]; + + const auto& mhaScaleS = tensors[miopenTensorMhaScaleS]; + const auto& mhaScaleO = tensors[miopenTensorMhaScaleO]; + + const auto& mhadp = tensors[miopenTensorMhaDropoutProbability]; + const auto& mhads = tensors[miopenTensorMhaDropoutSeed]; + const auto& mhado = tensors[miopenTensorMhaDropoutOffset]; + + mha::MhaInputDescsForward inputDescs = {mhaK->tensorFloat.desc, + mhaQ->tensorFloat.desc, + mhaV->tensorFloat.desc, + mhaDescaleK->tensorFloat.desc, + mhaDescaleQ->tensorFloat.desc, + mhaDescaleV->tensorFloat.desc, + mhaDescaleS->tensorFloat.desc, + mhaScaleS->tensorFloat.desc, + mhaScaleO->tensorFloat.desc, + scale, + mhadp->tensorFloat.desc, + mhads->tensorFloat.desc, + mhado->tensorFloat.desc, + tensors[miopenTensorMhaO]->tensorFloat.desc, + tensors[miopenTensorMhaAmaxO]->tensorFloat.desc, + tensors[miopenTensorMhaAmaxS]->tensorFloat.desc, + tensors[miopenTensorMhaM]->tensorFloat.desc, + tensors[miopenTensorMhaZInv]->tensorFloat.desc}; + + const mha::ProblemDescription problem_description = {inputDescs}; + + const auto invoke_ctx = [&]() -> AnyInvokeParams { + mha::MhaDataForward dataForward = { + mhaK->gpuBuffer.get(), + mhaQ->gpuBuffer.get(), + mhaV->gpuBuffer.get(), + mhaDescaleK->gpuBuffer.get(), + mhaDescaleQ->gpuBuffer.get(), + mhaDescaleV->gpuBuffer.get(), + mhaDescaleS->gpuBuffer.get(), + mhaScaleS->gpuBuffer.get(), + mhaScaleO->gpuBuffer.get(), + mhadp->gpuBuffer.get(), + mhads->gpuBuffer.get(), + mhado->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaO]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaAmaxO]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaAmaxS]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaM]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaZInv]->gpuBuffer.get()}; + + return mha::InvokeParams(dataForward, workspace.ptr(), workspace.size()); + }(); + + const auto net_cfg = problem_description.MakeNetworkConfig(); + const auto found_invoker = handle.GetInvoker(net_cfg, solver::Id(solver_id)); + + if(found_invoker) + { + (*found_invoker)(handle, invoke_ctx); + } + else + { + auto ctx = ExecutionContext{&handle}; + + solver::mha::MhaForward mhaForward; + + const auto mha_solution = mhaForward.GetSolution(ctx, problem_description); + + decltype(auto) invoker = handle.PrepareInvoker(*mha_solution.invoker_factory, + mha_solution.construction_params); + handle.RegisterInvoker(invoker, net_cfg, solver::Id(solver_id).ToString()); + invoker(handle, invoke_ctx); + } + + ReadData(handle, outputResultsMap); + } + + void GetBackwardResultsWithoutFind20(Handle& handle, + TensorStructMap& outputResultsMap, + Workspace& workspace, + uint64_t solver_id) + { + const auto& mhaK = tensors[miopenTensorMhaK]; + const auto& mhaQ = tensors[miopenTensorMhaQ]; + const auto& mhaV = tensors[miopenTensorMhaV]; + const auto& mhaO = tensors[miopenTensorMhaO]; + const auto& mhaDO = tensors[miopenTensorMhaDO]; + const auto& mhaM = tensors[miopenTensorMhaM]; + const auto& mhaZInv = tensors[miopenTensorMhaZInv]; + + const auto& mhaDescaleK = tensors[miopenTensorMhaDescaleK]; + const auto& mhaDescaleQ = tensors[miopenTensorMhaDescaleQ]; + const auto& mhaDescaleV = tensors[miopenTensorMhaDescaleV]; + const auto& mhaDescaleS = tensors[miopenTensorMhaDescaleS]; + const auto& mhaDescaleO = tensors[miopenTensorMhaDescaleO]; + const auto& mhaDescaleDO = tensors[miopenTensorMhaDescaleDO]; + const auto& mhaDescaleDS = tensors[miopenTensorMhaDescaleDS]; + + const auto& mhaScaleS = tensors[miopenTensorMhaScaleS]; + + const auto& mhaScaleDS = tensors[miopenTensorMhaScaleDS]; + const auto& mhaScaleDQ = tensors[miopenTensorMhaScaleDQ]; + const auto& mhaScaleDK = tensors[miopenTensorMhaScaleDK]; + const auto& mhaScaleDV = tensors[miopenTensorMhaScaleDV]; + + const auto& mhadp = tensors[miopenTensorMhaDropoutProbability]; + const auto& mhads = tensors[miopenTensorMhaDropoutSeed]; + const auto& mhado = tensors[miopenTensorMhaDropoutOffset]; + + mha::MhaInputDescsBackward inputDescs = {mhaK->tensorFloat.desc, + mhaQ->tensorFloat.desc, + mhaV->tensorFloat.desc, + mhaO->tensorFloat.desc, + mhaDO->tensorFloat.desc, + mhaM->tensorFloat.desc, + mhaZInv->tensorFloat.desc, + mhaDescaleK->tensorFloat.desc, + mhaDescaleQ->tensorFloat.desc, + mhaDescaleV->tensorFloat.desc, + mhaDescaleS->tensorFloat.desc, + mhaDescaleO->tensorFloat.desc, + mhaDescaleDO->tensorFloat.desc, + mhaDescaleDS->tensorFloat.desc, + mhaScaleS->tensorFloat.desc, + mhaScaleDS->tensorFloat.desc, + mhaScaleDQ->tensorFloat.desc, + mhaScaleDK->tensorFloat.desc, + mhaScaleDV->tensorFloat.desc, + scale, + mhadp->tensorFloat.desc, + mhads->tensorFloat.desc, + mhado->tensorFloat.desc, + tensors[miopenTensorMhaDQ]->tensorFloat.desc, + tensors[miopenTensorMhaDK]->tensorFloat.desc, + tensors[miopenTensorMhaDV]->tensorFloat.desc, + tensors[miopenTensorMhaAmaxDQ]->tensorFloat.desc, + tensors[miopenTensorMhaAmaxDK]->tensorFloat.desc, + tensors[miopenTensorMhaAmaxDV]->tensorFloat.desc, + tensors[miopenTensorMhaAmaxDS]->tensorFloat.desc}; + + const mha::ProblemDescription problem_description = {inputDescs}; + + const auto invoke_ctx = [&]() -> AnyInvokeParams { + mha::MhaDataBackward dataBackward = { + mhaK->gpuBuffer.get(), + mhaQ->gpuBuffer.get(), + mhaV->gpuBuffer.get(), + mhaO->gpuBuffer.get(), + mhaDO->gpuBuffer.get(), + mhaM->gpuBuffer.get(), + mhaZInv->gpuBuffer.get(), + mhaDescaleK->gpuBuffer.get(), + mhaDescaleQ->gpuBuffer.get(), + mhaDescaleV->gpuBuffer.get(), + mhaDescaleS->gpuBuffer.get(), + mhaDescaleO->gpuBuffer.get(), + mhaDescaleDO->gpuBuffer.get(), + mhaDescaleDS->gpuBuffer.get(), + mhaScaleS->gpuBuffer.get(), + mhaScaleDS->gpuBuffer.get(), + mhaScaleDQ->gpuBuffer.get(), + mhaScaleDK->gpuBuffer.get(), + mhaScaleDV->gpuBuffer.get(), + mhadp->gpuBuffer.get(), + mhads->gpuBuffer.get(), + mhado->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaDQ]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaDK]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaDV]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaAmaxDQ]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaAmaxDK]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaAmaxDV]->gpuBuffer.get(), + outputResultsMap[miopenTensorMhaAmaxDS]->gpuBuffer.get()}; + + return mha::InvokeParams(dataBackward, workspace.ptr(), workspace.size()); + }(); + + const auto net_cfg = problem_description.MakeNetworkConfig(); + const auto found_invoker = handle.GetInvoker(net_cfg, solver::Id(solver_id)); + + if(found_invoker) + { + (*found_invoker)(handle, invoke_ctx); + } + else + { + auto ctx = ExecutionContext{&handle}; + + solver::mha::MhaBackward mhaBackward; + + const auto mha_solution = mhaBackward.GetSolution(ctx, problem_description); + + decltype(auto) invoker = handle.PrepareInvoker(*mha_solution.invoker_factory, + mha_solution.construction_params); + handle.RegisterInvoker(invoker, net_cfg, solver::Id(solver_id).ToString()); + invoker(handle, invoke_ctx); + } + + ReadData(handle, outputResultsMap); + } + + void ReadData(Handle& handle, TensorStructMap& outputResultsMap) + { + for(const auto& it : outputResultsMap) + { + it.second->tensorFloat.data = + handle.Read(it.second->gpuBuffer, it.second->tensorFloat.data.size()); + } + } + +private: + TensorStructMap tensors; + + MhaDescriptor mha_descriptor; + + miopenProblem_t problem; + + bool isForward; + + const unsigned int test_n = 2; + const unsigned int test_h = 4; + const unsigned int test_s = 8; + const unsigned int test_d = 16; + + float scale = 1.0f; +}; + +TEST(TestMhaFind20, MhaForward) +{ + Handle& handle = get_handle(); + + MhaFind20Test test(true); + + std::vector solutions = test.TestFindSolutions(handle); + test.TestSolutionAttributes(solutions); + + test.TestRunSolutions(handle, solutions); + test.Finalize(); +} + +TEST(TestMhaFind20, MhaBackward) +{ + Handle& handle = get_handle(); + + MhaFind20Test test(false); + + std::vector solutions = test.TestFindSolutions(handle); + test.TestSolutionAttributes(solutions); + + test.TestRunSolutions(handle, solutions); + test.Finalize(); +} diff --git a/test/gtest/mha_helper.hpp b/test/gtest/mha_helper.hpp new file mode 100644 index 0000000000..efd1f7098c --- /dev/null +++ b/test/gtest/mha_helper.hpp @@ -0,0 +1,619 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "tensor_holder.hpp" +#include "conv_tensor_gen.hpp" + +#include +#include + +// disable __device__ qualifiers +#ifdef FQUALIFIERS +#error rocrand FQUALIFIERS defined externally, probably one of rocrand device header included prior to this +#endif +#define FQUALIFIERS inline +#include + +namespace test { +namespace cpu { + +using float8 = miopen_f8::hip_f8; + +struct CPUMHATestCase +{ + // represents total number of seq len present in batch + size_t batch_size; + size_t sequence_length; + size_t num_heads; + size_t problem_dimension; + float drop_out_rate; + + friend std::ostream& operator<<(std::ostream& os, const CPUMHATestCase& tc) + { + return os << "(batch_size: " << tc.batch_size << " num_heads:" << tc.num_heads + << " sequence_length:" << tc.sequence_length + << " problem_dimension:" << tc.problem_dimension + << " drop_out_rate:" << tc.drop_out_rate << " )"; + } +}; + +inline constexpr float GetF8Scaling(float max_val) +{ + constexpr float fp8_E4M3_max = 240.0f; + + return fp8_E4M3_max / max_val; +} + +template +T AbsoluteMax(const tensor& max_of_tensor) +{ + T maxVal = std::abs(max_of_tensor[0]); // Start with the first element as the maximum + max_of_tensor.for_each( + [&](auto... id) { maxVal = std::max(maxVal, std::abs(max_of_tensor(id...))); }); + return maxVal; +} + +template +void Dot_3D_3D(const tensor& A_mat, const tensor& B_mat, tensor& C_mat) +{ + size_t k_val = A_mat.desc.GetLengths()[2]; + assert(k_val == B_mat.desc.GetLengths()[1]); + C_mat.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t dk_id) { + double sum(0); + for(size_t k_id = 0; k_id < k_val; ++k_id) + { + sum += A_mat(b_id, sl_id, k_id) * B_mat(h_id, k_id, dk_id); + } + C_mat(b_id, h_id, sl_id, dk_id) = T(sum); + }); +} + +template +void Dot_3D_3D_T(const tensor& A_mat, const tensor& B_mat, tensor& C_mat) +{ + size_t k_val = A_mat.desc.GetLengths()[2]; + assert(k_val == B_mat.desc.GetLengths()[2]); + C_mat.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t dk_id) { + double sum(0); + for(size_t k_id = 0; k_id < k_val; ++k_id) + { + sum += A_mat(b_id, sl_id, k_id) * B_mat(h_id, dk_id, k_id); + } + C_mat(b_id, h_id, sl_id, dk_id) = T(sum); + }); +} + +template +void Dot_4D_4D_T(const tensor& A_mat, const tensor& B_mat, tensor& C_mat) +{ + size_t k_val = A_mat.desc.GetLengths()[3]; + assert(k_val == B_mat.desc.GetLengths()[3]); // since transpose + + C_mat.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t dk_id) { + double sum(0); + for(size_t k_id = 0; k_id < k_val; ++k_id) + { + sum += T2(A_mat(b_id, h_id, sl_id, k_id)) * T2(B_mat(b_id, h_id, dk_id, k_id)); + } + + C_mat(b_id, h_id, sl_id, dk_id) = T2(sum); + }); +} + +template +void Dot_4D_T_4D(const tensor& A_mat, const tensor& B_mat, tensor& C_mat) +{ + size_t k_val = A_mat.desc.GetLengths()[3]; + assert(k_val == B_mat.desc.GetLengths()[2]); // since transpose + + C_mat.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t dk_id) { + double sum(0); + for(size_t k_id = 0; k_id < k_val; ++k_id) + { + sum += T2(A_mat(b_id, h_id, k_id, sl_id)) * T2(B_mat(b_id, h_id, k_id, dk_id)); + } + + C_mat(b_id, h_id, sl_id, dk_id) = T2(sum); + }); +} + +template +void Dot_4D_4D(const tensor& A_mat, const tensor& B_mat, tensor& C_mat) +{ + size_t k_val = A_mat.desc.GetLengths()[3]; + assert(k_val == B_mat.desc.GetLengths()[2]); + C_mat.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t dk_id) { + double sum(0); + for(size_t k_id = 0; k_id < k_val; ++k_id) + { + sum += double(A_mat(b_id, h_id, sl_id, k_id)) * double(B_mat(b_id, h_id, k_id, dk_id)); + } + + C_mat(b_id, h_id, sl_id, dk_id) = T2(sum); + }); +} + +template +void Dot_3D_2D_T(const tensor& A_mat, const tensor& B_mat, tensor& C_mat) +{ + size_t k_val = A_mat.desc.GetLengths()[2]; + assert(k_val == B_mat.desc.GetLengths()[1]); + C_mat.par_for_each([&](size_t b_id, size_t s_id, size_t pd_id) { + double sum(0); + for(size_t k_id = 0; k_id < k_val; ++k_id) + { + sum += double(A_mat(b_id, s_id, k_id)) * double(B_mat(pd_id, k_id)); + } + + C_mat(b_id, s_id, pd_id) = T2(sum); + }); +} + +template +void AddMask4D_2D(tensor& mat_A_val, const tensor& mat_mask) +{ + mat_A_val.par_for_each([&](size_t b_id, size_t h_id, size_t sl_i_id, size_t sl_j_id) { + mat_A_val(b_id, h_id, sl_i_id, sl_j_id) += mat_mask(sl_i_id, sl_j_id); + }); +} + +template +void RowReductionMax(const tensor& A_mat, tensor& rrm_tensor) +{ + size_t sl_dim = A_mat.desc.GetLengths()[3]; + rrm_tensor.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t sl0_id) { + T max(A_mat(b_id, h_id, sl_id, sl0_id)); + for(size_t id = 0; id < sl_dim; ++id) + { + max = std::max(max, A_mat(b_id, h_id, sl_id, id)); + } + rrm_tensor(b_id, h_id, sl_id, sl0_id) = max; + }); +} + +template +void ScaleMult(const tensor& tensor_val, + const T2& scale_factor, + tensor& tensor_scale_factor) +{ + tensor_scale_factor.par_for_each( + [&](auto... id) { tensor_scale_factor(id...) = T3(tensor_val(id...) * scale_factor); }); +} + +template +void PointWiseExp(const tensor& tensor_val, tensor& tensor_exp_val) +{ + tensor_exp_val.par_for_each( + [&](auto... id) { tensor_exp_val(id...) = T(std::exp(tensor_val(id...))); }); +} + +template +void PointWiseMultiply(const tensor& tensor_a, const tensor& tensor_b, tensor& tensor_c) +{ + tensor_c.par_for_each([&](auto... id) { tensor_c(id...) = tensor_a(id...) * tensor_b(id...); }); +} + +template +void BroadCastSub(const tensor& tensor_val1, + const tensor& tesnor_val2, + tensor& tensor_val1_sub_val2) +{ + tensor_val1_sub_val2.par_for_each( + [&](size_t b_id, size_t h_id, size_t sl_i_id, size_t sl_j_id) { + tensor_val1_sub_val2(b_id, h_id, sl_i_id, sl_j_id) = + tensor_val1(b_id, h_id, sl_i_id, sl_j_id) - tesnor_val2(b_id, h_id, sl_i_id, 0); + }); +} + +template +void BroadCastAdd(const tensor& tensor_val1, + const tensor& tesnor_val2, + tensor& tensor_val1_sub_val2) +{ + tensor_val1_sub_val2.par_for_each( + [&](size_t b_id, size_t h_id, size_t sl_i_id, size_t sl_j_id) { + tensor_val1_sub_val2(b_id, h_id, sl_i_id, sl_j_id) = + tensor_val1(b_id, h_id, sl_i_id, sl_j_id) + tesnor_val2(b_id, h_id, sl_i_id, 0); + }); +} + +template +void BroadCastMul(const tensor& tensor_val, const tensor& z_sum, tensor& tensor_div_z_sum) +{ + tensor_div_z_sum.par_for_each([&](size_t b_id, size_t h_id, size_t sl_i_id, size_t sl_j_id) { + tensor_div_z_sum(b_id, h_id, sl_i_id, sl_j_id) = + tensor_val(b_id, h_id, sl_i_id, sl_j_id) * z_sum(b_id, h_id, sl_i_id, 0); + }); +} + +template +void RowReductionReciprocalSum(const tensor& A_mat, tensor& rrsum_tensor) +{ + size_t sl_dim = A_mat.desc.GetLengths()[3]; + rrsum_tensor.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t sl0_id) { + double sum(0); + for(size_t id = 0; id < sl_dim; ++id) + { + sum += A_mat(b_id, h_id, sl_id, id); + } + rrsum_tensor(b_id, h_id, sl_id, sl0_id) = static_cast(1) / sum; + }); +} + +template +void RowReductionSum(const tensor& A_mat, tensor& rrsum_tensor) +{ + size_t sl_dim = A_mat.desc.GetLengths()[3]; + rrsum_tensor.par_for_each([&](size_t b_id, size_t h_id, size_t sl_id, size_t sl0_id) { + double sum(0); + for(size_t id = 0; id < sl_dim; ++id) + { + sum += A_mat(b_id, h_id, sl_id, id); + } + rrsum_tensor(b_id, h_id, sl_id, sl0_id) = sum; + }); +} + +// template +// void SetupMask(tensor& mask) +// { +// mask.par_for_each([&](size_t s_id, size_t p_id) { +// // make anything above diagonal inf +// if(p_id > s_id) +// { +// mask(s_id, p_id) = -std::numeric_limits::infinity(); +// } +// }); +// } + +template +void DropOut(tensor& tensor, float dropout_rate, uint64_t seed, uint64_t offset) +{ + if(dropout_rate > 0.0f) + { + // it assumes that 'blockIdx.x * blockDim.x + threadIdx.x' will cover whole tensor + // and without idle threads + std::vector rng(tensor.data.size()); + for(size_t idx = 0; idx < tensor.data.size(); ++idx) + { + rocrand_init(prng::hash(seed + idx), 0, offset, &rng[idx]); + } + + size_t idx = 0; + tensor.for_each([&, scale = 1.0f / (1.0f - dropout_rate)](auto... id) { + const bool drop = prng::xorwow_uniform(&rng[idx++]) < dropout_rate; + tensor(id...) = drop ? T(0) : tensor(id...) * scale; + }); + } +} + +template +void Concat(const tensor& A_mat, tensor& B_mat) +{ + const auto& dims = A_mat.desc.GetLengths(); + size_t d_k = dims[3]; + + A_mat.par_for_each([&](size_t b_id, size_t h_id, size_t s_id, size_t dk_id) { + B_mat(b_id, s_id, h_id * d_k + dk_id) = A_mat(b_id, h_id, s_id, dk_id); + }); +} + +/* attn_max : max_of_each_row_of(q_dot_k_transpose) + * Its a row reduction operation. eg: (3x3) => (3x1) + * z_sum : sum(exp((q_dot_k_transpose - attn_max))) eg: (3x3) => (3x1) + * Its a row reduction operation. eg: (3x3) => (3x1) + */ +template +void SoftMax(const tensor& q_dot_k_transpose, + tensor& softmax, + tensor& attn_max, + tensor& z_sum) +{ + // compute max across each row of matrix. This value is + // used for numerical stability for softmax computation. + RowReductionMax(q_dot_k_transpose, attn_max); + + // substract the computed max + auto q_dot_k_transpose_sub_attn_max = q_dot_k_transpose; + BroadCastSub(q_dot_k_transpose, attn_max, q_dot_k_transpose_sub_attn_max); + + // Compute the exponential of each element + // exp(row_max) + auto exp_q_dot_k_transpose_sub_attn_max = q_dot_k_transpose_sub_attn_max; + PointWiseExp(q_dot_k_transpose_sub_attn_max, exp_q_dot_k_transpose_sub_attn_max); + + // z_sum aka attn_norm = 1 / sum(exp((q_dot_k_transpose - attn_max))) + RowReductionReciprocalSum(exp_q_dot_k_transpose_sub_attn_max, z_sum); + + // softmax = exp((q_dot_k_transpose - attn_max)) * z_sum + BroadCastMul(exp_q_dot_k_transpose_sub_attn_max, z_sum, softmax); +} + +template +void MultiHeadAttentionfp8(const tensor& q_val, + const tensor& k_val, + const tensor& v_val, + tensor& softmax, + tensor& attn_max, + tensor& Z_sum, + float q_descale, + float k_descale, + float v_descale, + float s_descale, + float s_scale, + float o_scale, + float dropout_rate, + uint64_t seed, + uint64_t offset, + float& aMax_S, + float& aMax_O, + tensor& multi_head_attention_fp8) +{ + auto inputLengths = q_val.desc.GetLengths(); + inputLengths[3] = inputLengths[2]; // NHSD converting to NHSS + tensor q_dot_k_fp8_stored_in_fp32_tensor(inputLengths); + + // The FP8 Matrix Multiplicatin happens here. + // The results are tored in fp32 tensor. + // 1) fp8 matrix multiplication + Dot_4D_4D_T(q_val, k_val, q_dot_k_fp8_stored_in_fp32_tensor); + + // bring it back to fp32 so that we can do the softmax + ScaleMult(q_dot_k_fp8_stored_in_fp32_tensor, + q_descale * k_descale, + q_dot_k_fp8_stored_in_fp32_tensor); + + SoftMax(q_dot_k_fp8_stored_in_fp32_tensor, softmax, attn_max, Z_sum); + + // Get scaling for softmax + aMax_S = AbsoluteMax(softmax); + + // drop out + DropOut(softmax, dropout_rate, seed, offset); + + tensor softmax_fp8(softmax.desc.GetLengths()); + // get fp8 version of Softmax(Q.dot(K_transpose)) and V + ScaleMult(softmax, s_scale, softmax_fp8); + + tensor atten_heads_fp32(multi_head_attention_fp8.desc.GetLengths()); + // 2) fp8 matrix multiplication + Dot_4D_4D(softmax_fp8, v_val, atten_heads_fp32); + + // bring it back to fp32 + ScaleMult(atten_heads_fp32, s_descale * v_descale, atten_heads_fp32); + aMax_O = AbsoluteMax(atten_heads_fp32); + + // scale to fp8 version + ScaleMult(atten_heads_fp32, o_scale, multi_head_attention_fp8); +} + +template +void MultiHeadAttentionf32(const tensor& q_val, + const tensor& k_val, + const tensor& v_val, + tensor& q_dot_k_transpose, + tensor& softmax, + tensor& attn_max, + tensor& Z_sum, + float& aMax_S, + float& aMax_O, + tensor& multi_head_attention) +{ + + Dot_4D_4D_T(q_val, k_val, q_dot_k_transpose); + + SoftMax(q_dot_k_transpose, softmax, attn_max, Z_sum); + aMax_S = AbsoluteMax(softmax); + + // // drop out + // // DropOut(softmax, cpu_mha_test_case.drop_out_rate); + + // // // drop out scalse + // // double drop_out_scale = 1.0 / (1.0 - cpu_mha_test_case.drop_out_rate); + // // Scale(softmax, drop_out_scale); + + // O = (Q.dot(Kt)).dot(V) + Dot_4D_4D(softmax, v_val, multi_head_attention); + aMax_O = AbsoluteMax(multi_head_attention); +} + +template +void MultiHeadAttentionBackwardDataf32(const tensor& q_val, + const tensor& k_val, + const tensor& v_val, + const tensor& O_val, // attention (O) + const tensor& dO_val, + const tensor& softmax, + const tensor& attn_max, + const tensor& z_sum, + tensor& dQ_val, + tensor& dK_val, + tensor& dV_val) +{ + + auto inputLengths = q_val.desc.GetLengths(); + inputLengths[3] = inputLengths[2]; // NHSD converting to NHSS + + tensor dO_dot_V_tranpsoe_val(inputLengths); + tensor bwd_intermediate(inputLengths); + tensor dO_pointwiseMul_O(dO_val.desc.GetLengths()); + inputLengths[3] = 1; // NHSD converting to NHS1 for row reduction tensor + tensor dO_pointwiseMul_O_rrsum(inputLengths); + + // softmax_T.dO + + // 1) fp8 matrix multiplication + Dot_4D_T_4D(softmax, dO_val, dV_val); + + // dO x V + Dot_4D_4D_T(dO_val, v_val, dO_dot_V_tranpsoe_val); + + // dO . O + PointWiseMultiply(dO_val, O_val, dO_pointwiseMul_O); + + RowReductionSum(dO_pointwiseMul_O, dO_pointwiseMul_O_rrsum); + BroadCastSub(dO_dot_V_tranpsoe_val, dO_pointwiseMul_O_rrsum, bwd_intermediate); + PointWiseMultiply(bwd_intermediate, softmax, bwd_intermediate); + + // both dk and dQ + // finally + Dot_4D_4D(bwd_intermediate, k_val, dQ_val); + Dot_4D_T_4D(bwd_intermediate, q_val, dK_val); +} + +template +void MultiHeadAttentionBackwardDataf8(const tensor& q_val, + const tensor& k_val, + const tensor& v_val, + const tensor& O_val, // attention (O) + const tensor& dO_val, + const tensor& softmax_fp32, + float q_descale, + float k_descale, + float v_descale, + float dQ_scale, + float dK_scale, + float dV_scale, + float s_scale, + float s_descale, + float ds_scale, + float ds_descale, + float O_descale, + float dO_descale, + float& aMax_dS, + float& aMax_dQ, + float& aMax_dK, + float& aMax_dV, + tensor& dQ_val, + tensor& dK_val, + tensor& dV_val) +{ + + // Calculate dV_val = softmax_T x dO + + tensor softmax_fp8(softmax_fp32.desc.GetLengths()); + tensor softmax_dot_dO_fp8(dV_val.desc.GetLengths()); + ScaleMult(softmax_fp32, s_scale, softmax_fp8); + // fp8 matrix multiplication + Dot_4D_T_4D(softmax_fp8, dO_val, softmax_dot_dO_fp8); + + tensor softmax_dO_fp32(dV_val.desc.GetLengths()); + ScaleMult(softmax_dot_dO_fp8, s_descale * dO_descale, softmax_dO_fp32); + + aMax_dV = AbsoluteMax(softmax_dO_fp32); + + ScaleMult(softmax_dO_fp32, dV_scale, dV_val); + + auto inputLengths = q_val.desc.GetLengths(); + inputLengths[3] = inputLengths[2]; // NHSD converting to NHSS + + // Calculate dQ_val and dK_val + // dO x V + tensor dO_dot_V_tranpose_val(inputLengths); + tensor dO_dot_V_tranpose_val_fp32(inputLengths); + // fp8 matrix multiplication + Dot_4D_4D_T(dO_val, v_val, dO_dot_V_tranpose_val); + // Descale dO, Descale V + ScaleMult(dO_dot_V_tranpose_val, dO_descale * v_descale, dO_dot_V_tranpose_val_fp32); + + // dO . O + auto o_valLengths = O_val.desc.GetLengths(); + o_valLengths[3] = 1; // NHSD converting to NHS1 for row reduction tensor + tensor dO_pointwiseMul_O_rrsum_fp32(o_valLengths); + + tensor o_val_fp32(O_val.desc.GetLengths()); + tensor dO_val_fp32(dO_val.desc.GetLengths()); + // Descale dO + ScaleMult(O_val, O_descale, o_val_fp32); + // Descale O + ScaleMult(dO_val, dO_descale, dO_val_fp32); + tensor dO_pointwiseMul_O_fp32(dO_val.desc.GetLengths()); + PointWiseMultiply(dO_val_fp32, o_val_fp32, dO_pointwiseMul_O_fp32); + RowReductionSum(dO_pointwiseMul_O_fp32, dO_pointwiseMul_O_rrsum_fp32); + + // Bias Substraction + + tensor bias_sub_fp32(inputLengths); + BroadCastSub(dO_dot_V_tranpose_val_fp32, dO_pointwiseMul_O_rrsum_fp32, bias_sub_fp32); + + tensor bias_sub_fp32_pm_softmax(inputLengths); + tensor bias_sub_fp8_pm_softmax(inputLengths); + PointWiseMultiply(bias_sub_fp32, softmax_fp32, bias_sub_fp32_pm_softmax); + // AMax_dS + aMax_dS = AbsoluteMax(bias_sub_fp32_pm_softmax); + + // s_scale to convert to fp8 + ScaleMult(bias_sub_fp32_pm_softmax, ds_scale, bias_sub_fp8_pm_softmax); + + // fp8 matrix multiplication + Dot_4D_4D(bias_sub_fp8_pm_softmax, k_val, dQ_val); + // fp8 matrix multiplication + Dot_4D_T_4D(bias_sub_fp8_pm_softmax, q_val, dK_val); + + tensor dQ_val_fp32(dQ_val.desc.GetLengths()); + tensor dK_val_fp32(dK_val.desc.GetLengths()); + // bring it back to fp32 + ScaleMult(dQ_val, ds_descale * k_descale, dQ_val_fp32); + ScaleMult(dK_val, ds_descale * q_descale, dK_val_fp32); + + aMax_dQ = AbsoluteMax(dQ_val_fp32); + aMax_dK = AbsoluteMax(dK_val_fp32); + + ScaleMult(dQ_val_fp32, dQ_scale, dQ_val); + ScaleMult(dK_val_fp32, dK_scale, dK_val); +} + +template +tensor ExtractGoldenDataFromJson(std::string_view json_attention_data, + const tensor& tensor_val) +{ + auto jsonTensor = nlohmann::json::parse(json_attention_data); + // Check if the "tensor" key exists and is an array + EXPECT_TRUE(jsonTensor.contains("tensor") && jsonTensor["tensor"].is_array()) + << "Malformed ref data: 'tensor' key not found or is not an array"; + + // Extract the 2D array and flatten it + tensor res(tensor_val.desc); + res.data.clear(); // tensor constructed with .resize(), but we need push_back + for(const auto& row : jsonTensor["tensor"]) + { + EXPECT_TRUE(row.is_array()) + << "Malformed ref data: expected a row to be an array, but found a different type"; + for(const auto& val : row) + { + EXPECT_TRUE(val.is_number()) << "Malformed ref data: expected a value to be a " + "number, but found a different type"; + res.data.emplace_back(val.get()); + } + } + + EXPECT_EQ(res.data.size(), tensor_val.data.size()) + << "Malformed ref data: reference tensor has different size"; + + return res; +} + +} // namespace cpu +} // namespace test diff --git a/test/gtest/reduce_custom_fp32.cpp b/test/gtest/reduce_custom_fp32.cpp new file mode 100644 index 0000000000..17a1ff2beb --- /dev/null +++ b/test/gtest/reduce_custom_fp32.cpp @@ -0,0 +1,100 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../reduce_test.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace reduce_custom_fp32 { +std::vector GetArgs(const std::string& param) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + return {begin, end}; +} + +std::vector GetTestCases(void) +{ + const std::string& cmd = "test_reduce_test "; + const std::string& float_arg = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)); + + // clang-format off + return std::vector{ + {cmd + float_arg + " --scales 1 0 --CompType 1 --D 1024 30528 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2"} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithFloat_reduce_custom_fp32 : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() && + miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--float")) + { + GTEST_SKIP(); + } + std::vector params = ConfigWithFloat_reduce_custom_fp32::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens = GetArgs(test_value); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace reduce_custom_fp32 +using namespace reduce_custom_fp32; + +TEST_P(ConfigWithFloat_reduce_custom_fp32, FloatTest_reduce_custom_fp32) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(ReduceCustomFp32, + ConfigWithFloat_reduce_custom_fp32, + testing::Values(GetTestCases())); diff --git a/test/gtest/reduce_custom_fp32_fp16.cpp b/test/gtest/reduce_custom_fp32_fp16.cpp new file mode 100644 index 0000000000..819bf3f80a --- /dev/null +++ b/test/gtest/reduce_custom_fp32_fp16.cpp @@ -0,0 +1,162 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../reduce_test.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace reduce_custom_fp32_fp16 { +std::vector GetArgs(const std::string& param) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + return {begin, end}; +} + +std::vector GetTestCases(const std::string& precision) +{ + const std::string& cmd = "test_reduce_test " + precision; + + // clang-format off + return std::vector{ + {cmd + " --scales 1 0 --CompType 1 --D 8 2 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 160 10 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 7 1024 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 1 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 1 1 --I 0 --N 1 ---ReduceOp 1 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 1 1 --I 1 --N 1 ---ReduceOp 3 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 2 1 --I 1 --N 1 ---ReduceOp 3 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 6 2 1 --I 0 --N 1 ---ReduceOp 3 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 6 2 1 --I 0 --N 1 ---ReduceOp 2 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 2 2 1 --I 0 --N 1 ---ReduceOp 0 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 4 3 1 --I 0 --N 1 ---ReduceOp 3 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 3 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 3 --R 0 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 2048 32 1 --I 0 --N 1 ---ReduceOp 3 --R 0 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 4 3 1 --I 0 --N 1 ---ReduceOp 2 --R 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 2 --R 0 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 2048 32 1 --I 0 --N 1 ---ReduceOp 2 --R 0 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 3 4 1 --I 0 --N 1 ---ReduceOp 2 --R 0 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 12 11 1 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2"}, + {cmd + " --scales 1 0 --CompType 1 --D 13 4 7 7 --I 0 --N 1 ---ReduceOp 0 --R 0 1 2 3"}, + {cmd + " --scales 1 0 --CompType 1 --D 64 3 280 81 --I 0 --N 0 --ReduceOp 0 --R 0"} + }; + // clang-format on +} + +class ConfigWithFloat_reduce_custom_fp32_fp16 + : public testing::TestWithParam> +{ +}; + +class ConfigWithHalf_reduce_custom_fp32_fp16 + : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice(void) +{ + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(miopenDataType_t prec) +{ + std::vector params; + switch(prec) + { + case miopenFloat: params = ConfigWithFloat_reduce_custom_fp32_fp16::GetParam(); break; + case miopenHalf: params = ConfigWithHalf_reduce_custom_fp32_fp16::GetParam(); break; + case miopenInt8: + case miopenBFloat16: + case miopenInt32: + case miopenDouble: + case miopenFloat8: + case miopenBFloat8: + default: + FAIL() << "miopenInt8, miopenBFloat16, miopenInt32, " + "miopenDouble, miopenFloat8, miopenBFloat8 " + "data type not supported by reduce_custom_fp32_fp16 test"; + } + + for(const auto& test_value : params) + { + std::vector tokens = GetArgs(test_value); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace reduce_custom_fp32_fp16 +using namespace reduce_custom_fp32_fp16; + +TEST_P(ConfigWithFloat_reduce_custom_fp32_fp16, FloatTest_reduce_custom_fp32_fp16) +{ + if(!(IsTestSupportedForDevice() // + && (miopen::IsUnset(ENV(MIOPEN_TEST_ALL)) // standalone run + || (miopen::IsEnabled(ENV(MIOPEN_TEST_ALL)) // or --float full tests enabled + && miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--float")))) + { + GTEST_SKIP(); + } + + Run2dDriver(miopenFloat); +}; + +TEST_P(ConfigWithHalf_reduce_custom_fp32_fp16, HalfTest_reduce_custom_fp32_fp16) +{ + if(!(IsTestSupportedForDevice() // + && (miopen::IsUnset(ENV(MIOPEN_TEST_ALL)) // standalone run + || (miopen::IsEnabled(ENV(MIOPEN_TEST_ALL)) // or --half full tests enabled + && miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--half")))) + { + GTEST_SKIP(); + } + + Run2dDriver(miopenHalf); +}; + +INSTANTIATE_TEST_SUITE_P(ReduceCustomFp32, + ConfigWithFloat_reduce_custom_fp32_fp16, + testing::Values(GetTestCases("--float"))); + +INSTANTIATE_TEST_SUITE_P(ReduceCustomFp32, + ConfigWithHalf_reduce_custom_fp32_fp16, + testing::Values(GetTestCases("--half"))); diff --git a/test/gtest/regression_half_mi200.cpp b/test/gtest/regression_half_mi200.cpp new file mode 100644 index 0000000000..ddc9d54149 --- /dev/null +++ b/test/gtest/regression_half_mi200.cpp @@ -0,0 +1,110 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../conv2d.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace regression_half_mi200 { +void SetupEnvVar(void) +{ + miopen::UpdateEnvVar(ENV(MIOPEN_FIND_MODE), std::string("normal")); + miopen::UpdateEnvVar(ENV(MIOPEN_DEBUG_FIND_ONLY_SOLVER), + std::string("ConvHipImplicitGemmForwardV4R4Xdlops")); +} + +std::vector GetArgs(const std::string& param) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + return {begin, end}; +} + +std::vector GetTestCases(void) +{ + const std::string& cmd = "test_conv2d "; + const std::string& float_arg = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)); + const std::string& args = + " --verbose --disable-forward --disable-backward-data --disable-validation"; + + // clang-format off + return std::vector{ + {cmd + float_arg + " --cmode conv --pmode default --input 120 64 75 75 --weights 128 64 1 1 --pads_strides_dilations 0 0 2 2 1 1" + args} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithHalf_regression_half_mi200 : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() && + miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--half")) + { + GTEST_SKIP(); + } + SetupEnvVar(); + std::vector params = ConfigWithHalf_regression_half_mi200::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens = GetArgs(test_value); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace regression_half_mi200 +using namespace regression_half_mi200; + +TEST_P(ConfigWithHalf_regression_half_mi200, FloatTest_regression_half_mi200) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(RegressionHalfMi200, + ConfigWithHalf_regression_half_mi200, + testing::Values(GetTestCases())); diff --git a/test/gtest/regression_half_vega.cpp b/test/gtest/regression_half_vega.cpp new file mode 100644 index 0000000000..4077781f68 --- /dev/null +++ b/test/gtest/regression_half_vega.cpp @@ -0,0 +1,112 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../conv3d.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_ALL) +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace regression_half_vega { +void SetupEnvVar(void) +{ + miopen::UpdateEnvVar(ENV(MIOPEN_FIND_MODE), std::string("normal")); + miopen::UpdateEnvVar(ENV(MIOPEN_DEBUG_FIND_ONLY_SOLVER), std::string("GemmBwdRest")); +} + +std::vector GetArgs(const std::string& param) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + return {begin, end}; +} + +std::vector GetTestCases(void) +{ + const std::string& cmd = "test_conv3d "; + const std::string& float_arg = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)); + const std::string& conv_verbose_b = + float_arg + " --verbose --disable-forward --disable-backward-weights"; + + // clang-format off + return std::vector{ + {cmd + conv_verbose_b + " --cmode conv --pmode default --group-count 1 --batch_size 2 --input_channels 64 --output_channels 32 --spatial_dim_elements 128 128 128 --filter_dims 3 3 3 --pads_strides_dilations 1 1 1 1 1 1 1 1 1 --trans_output_pads 0 0 0 --in_layout NCDHW --fil_layout NCDHW --out_layout NCDHW"} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithHalf_regression_half_vega : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() // + && (miopen::IsUnset(ENV(MIOPEN_TEST_ALL)) // standalone run + || (miopen::IsEnabled(ENV(MIOPEN_TEST_ALL)) // or --float full tests enabled + && miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--half")))) + { + GTEST_SKIP(); + } + SetupEnvVar(); + std::vector params = ConfigWithHalf_regression_half_vega::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens = GetArgs(test_value); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace regression_half_vega +using namespace regression_half_vega; + +TEST_P(ConfigWithHalf_regression_half_vega, FloatTest_regression_half_vega) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(RegressionHalfVega, + ConfigWithHalf_regression_half_vega, + testing::Values(GetTestCases())); diff --git a/test/gtest/regression_issue_2012.cpp b/test/gtest/regression_issue_2012.cpp new file mode 100644 index 0000000000..be562664b8 --- /dev/null +++ b/test/gtest/regression_issue_2012.cpp @@ -0,0 +1,111 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "../conv2d.hpp" +#include +#include +#include +#include +#include "get_handle.hpp" + +MIOPEN_DECLARE_ENV_VAR_STR(MIOPEN_TEST_FLOAT_ARG) + +namespace regression_issue_2012 { +void SetupEnvVar(void) { miopen::UpdateEnvVar(ENV(MIOPEN_FIND_MODE), std::string("normal")); } + +std::vector GetArgs(const std::string& param) +{ + std::stringstream ss(param); + std::istream_iterator begin(ss); + std::istream_iterator end; + return {begin, end}; +} + +std::vector GetTestCases(void) +{ + const std::string& cmd = "test_conv2d "; + const std::string& float_arg = miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)); + const std::string& args = + " --verbose --disable-forward --disable-backward-data --disable-validation"; + + // clang-format off + return std::vector{ + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 128, 832, 7, 7 --weights 32, 832, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args}, + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 64, 192, 28, 28 --weights 64, 192, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args}, + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 64, 256, 28, 28 --weights 128, 256, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args}, + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 64, 480, 14, 14 --weights 64, 480, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args}, + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 64, 512, 14, 14 --weights 128, 512, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args}, + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 64, 512, 28, 28 --weights 128, 512, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args}, + {cmd + float_arg + " --cmode conv --pmode default --group-count 1 --input 64, 64, 56, 56 --weights 256, 64, 1, 1 --pads_strides_dilations 0 0 1 1 1 1" + args} + }; + // clang-format on +} + +using TestCase = decltype(GetTestCases())::value_type; + +class ConfigWithFloat_regression_issue_2012 : public testing::TestWithParam> +{ +}; + +bool IsTestSupportedForDevice() +{ + using e_mask = enabled; + using d_mask = disabled; + return ::IsTestSupportedForDevMask(); +} + +void Run2dDriver(void) +{ + if(!(IsTestSupportedForDevice() && + miopen::GetStringEnv(ENV(MIOPEN_TEST_FLOAT_ARG)) == "--float")) + { + GTEST_SKIP(); + } + SetupEnvVar(); + std::vector params = ConfigWithFloat_regression_issue_2012::GetParam(); + + for(const auto& test_value : params) + { + std::vector tokens = GetArgs(test_value); + std::vector ptrs; + + std::transform(tokens.begin(), tokens.end(), std::back_inserter(ptrs), [](const auto& str) { + return str.data(); + }); + testing::internal::CaptureStderr(); + test_drive(ptrs.size(), ptrs.data()); + auto capture = testing::internal::GetCapturedStderr(); + std::cout << capture; + } +}; + +} // namespace regression_issue_2012 +using namespace regression_issue_2012; + +TEST_P(ConfigWithFloat_regression_issue_2012, FloatTest_regression_issue_2012) { Run2dDriver(); }; + +INSTANTIATE_TEST_SUITE_P(RegressionIssue2012, + ConfigWithFloat_regression_issue_2012, + testing::Values(GetTestCases())); diff --git a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicFwdDlopsNCHWC.cpp b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicFwdDlopsNCHWC.cpp index 6d933c7f7f..83a0230234 100644 --- a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicFwdDlopsNCHWC.cpp +++ b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicFwdDlopsNCHWC.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -56,7 +54,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlops.cpp b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlops.cpp index de2eb9311a..b78b5cc3ec 100644 --- a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlops.cpp +++ b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlops.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -59,7 +57,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_bf16.cpp b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_bf16.cpp index 1e17b17a34..205e622cd5 100644 --- a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_bf16.cpp +++ b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_bf16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -60,7 +58,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_fp32_fp16.cpp b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_fp32_fp16.cpp index 050c53fb1e..f834f7cdf1 100644 --- a/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_fp32_fp16.cpp +++ b/test/gtest/smoke_solver_ConvAsmImplicitGemmGTCDynamicXdlopsNHWC_fp32_fp16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -60,7 +58,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvAsmImplicitGemmV4R1Dynamic.cpp b/test/gtest/smoke_solver_ConvAsmImplicitGemmV4R1Dynamic.cpp index 6266b39a27..9981f4114b 100644 --- a/test/gtest/smoke_solver_ConvAsmImplicitGemmV4R1Dynamic.cpp +++ b/test/gtest/smoke_solver_ConvAsmImplicitGemmV4R1Dynamic.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -58,7 +56,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvAsm_5x10_7x7.cpp b/test/gtest/smoke_solver_ConvAsm_5x10_7x7.cpp index e10035b141..370f9ceda6 100644 --- a/test/gtest/smoke_solver_ConvAsm_5x10_7x7.cpp +++ b/test/gtest/smoke_solver_ConvAsm_5x10_7x7.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -61,7 +59,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinograd3x3U.cpp b/test/gtest/smoke_solver_ConvBinWinograd3x3U.cpp index 6f5ce6dcbc..37ccc64191 100644 --- a/test/gtest/smoke_solver_ConvBinWinograd3x3U.cpp +++ b/test/gtest/smoke_solver_ConvBinWinograd3x3U.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -54,7 +52,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxS_fp16.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxS_fp16.cpp index 63692f5363..d07158466d 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxS_fp16.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxS_fp16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -54,7 +52,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxS_fp32.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxS_fp32.cpp index 41e0c3f7fe..c6da79e4b0 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxS_fp32.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxS_fp32.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -56,7 +54,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3.cpp index b2acd884e2..4f684244bb 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3.cpp @@ -31,8 +31,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -53,7 +51,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1.cpp index bfcf01837b..cee1eeef80 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -51,7 +49,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f16.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f16.cpp index e0e8c01748..39f0028bc9 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f16.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -66,7 +64,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f32.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f32.cpp index 53324bd3cb..e12fd22079 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f32.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxSf2x3g1_3x2_f32.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -63,7 +61,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvBinWinogradRxSf3x2.cpp b/test/gtest/smoke_solver_ConvBinWinogradRxSf3x2.cpp index 0b4e4feed4..0e75aafe4f 100644 --- a/test/gtest/smoke_solver_ConvBinWinogradRxSf3x2.cpp +++ b/test/gtest/smoke_solver_ConvBinWinogradRxSf3x2.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -52,7 +50,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_ConvCkIgemmFwdV6r1DlopsNchw.cpp b/test/gtest/smoke_solver_ConvCkIgemmFwdV6r1DlopsNchw.cpp index a20ee537ac..49ce1ac74f 100644 --- a/test/gtest/smoke_solver_ConvCkIgemmFwdV6r1DlopsNchw.cpp +++ b/test/gtest/smoke_solver_ConvCkIgemmFwdV6r1DlopsNchw.cpp @@ -31,8 +31,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1.cpp index 26a4d2bf91..8e5cab0b12 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1.cpp @@ -31,8 +31,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1Xdlops.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1Xdlops.cpp index 84fbcb5130..ee259e7631 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1Xdlops.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV1R1Xdlops.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV4R1.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV4R1.cpp index 537de627a2..40df018371 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV4R1.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmBwdDataV4R1.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmDataV4RxXdlops.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmDataV4RxXdlops.cpp index 1a7051c31f..ee762af6dc 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmDataV4RxXdlops.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmDataV4RxXdlops.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp16_bf16.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp16_bf16.cpp index 1c0c9d52c6..c732771eeb 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp16_bf16.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp16_bf16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -41,7 +39,7 @@ auto GetTestCases() // solver is not broken. smoke_solver_ConvHipImplicitGemmV4R1Fwd is split to BF16+FP16 and // FP32 tests because of WORKAROUND_ISSUE_2038, which disables validation of FP16 and BF16 // datatypes in this test, see - // https://github.com/ROCmSoftwarePlatform/MIOpen/pull/2043#issuecomment-1482657160 + // https://github.com/ROCm/MIOpen/pull/2043#issuecomment-1482657160 const auto env_fwd = std::tuple{ std::pair{ENV(MIOPEN_FIND_ENFORCE), std::string_view("SEARCH_DB_UPDATE")}, std::pair{ENV(MIOPEN_DEBUG_TUNING_ITERATIONS_MAX), std::string_view("5")}, diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp32.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp32.cpp index d809d3d040..5a8d52c377 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp32.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1Fwd_fp32.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1WrW.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1WrW.cpp index f0bf68c783..c1bc71dab0 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1WrW.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R1WrW.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R4.cpp b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R4.cpp index 07699c0958..00d17d78f8 100644 --- a/test/gtest/smoke_solver_ConvHipImplicitGemmV4R4.cpp +++ b/test/gtest/smoke_solver_ConvHipImplicitGemmV4R4.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() diff --git a/test/gtest/smoke_solver_ConvWinogradFuryRxSf2x3_f16.cpp b/test/gtest/smoke_solver_ConvWinogradFuryRxSf2x3_f16.cpp index 47ed5aebf4..666babd29d 100644 --- a/test/gtest/smoke_solver_ConvWinogradFuryRxSf2x3_f16.cpp +++ b/test/gtest/smoke_solver_ConvWinogradFuryRxSf2x3_f16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -53,7 +51,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_convasm.cpp b/test/gtest/smoke_solver_convasm.cpp index 085417fed5..b7892daece 100644 --- a/test/gtest/smoke_solver_convasm.cpp +++ b/test/gtest/smoke_solver_convasm.cpp @@ -31,8 +31,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -64,7 +62,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_convasm1x1u.cpp b/test/gtest/smoke_solver_convasm1x1u.cpp index 83f86c598b..3027400142 100644 --- a/test/gtest/smoke_solver_convasm1x1u.cpp +++ b/test/gtest/smoke_solver_convasm1x1u.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -56,7 +54,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_convasmbwdwrw.cpp b/test/gtest/smoke_solver_convasmbwdwrw.cpp index e867323f92..a3de04f741 100644 --- a/test/gtest/smoke_solver_convasmbwdwrw.cpp +++ b/test/gtest/smoke_solver_convasmbwdwrw.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -53,7 +51,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_convasmbwdwrw3x3_fp16.cpp b/test/gtest/smoke_solver_convasmbwdwrw3x3_fp16.cpp index 452d62fea6..6b214aef6f 100644 --- a/test/gtest/smoke_solver_convasmbwdwrw3x3_fp16.cpp +++ b/test/gtest/smoke_solver_convasmbwdwrw3x3_fp16.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -53,7 +51,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/smoke_solver_convasmbwdwrw3x3_fp32.cpp b/test/gtest/smoke_solver_convasmbwdwrw3x3_fp32.cpp index 85f91b6b01..f980265480 100644 --- a/test/gtest/smoke_solver_convasmbwdwrw3x3_fp32.cpp +++ b/test/gtest/smoke_solver_convasmbwdwrw3x3_fp32.cpp @@ -30,8 +30,6 @@ #include "../conv2d.hpp" -MIOPEN_DECLARE_ENV_VAR_BOOL(MIOPEN_TEST_GPU_XNACK_ENABLED) - namespace { auto GetTestCases() @@ -53,7 +51,7 @@ auto GetTestCases() using TestCase = decltype(GetTestCases())::value_type; -bool SkipTest() { return miopen::IsEnabled(ENV(MIOPEN_TEST_GPU_XNACK_ENABLED)); } +bool SkipTest() { return get_handle_xnack(); } bool IsTestSupportedForDevice() { diff --git a/test/gtest/softmax_find20.cpp b/test/gtest/softmax_find20.cpp new file mode 100644 index 0000000000..c88afd73cf --- /dev/null +++ b/test/gtest/softmax_find20.cpp @@ -0,0 +1,327 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2024 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 "test.hpp" +#include "get_handle.hpp" +#include "tensor_holder.hpp" +#include "verify.hpp" + +#include + +#include + +#include + +#include + +#include + +using namespace miopen; + +class SoftmaxFind20Test +{ +public: + SoftmaxFind20Test(bool forward) : problem(nullptr), isForward(forward) { Initialize(); } + + void AddTensorDescriptors() + { + std::cerr << "Creating softmax tensor descriptors..." << std::endl; + + auto test_set_tensor_descriptor = [this](miopenTensorArgumentId_t name, + TensorDescriptor& desc) { + EXPECT_EQUAL(miopenSetProblemTensorDescriptor(problem, name, &desc), + miopenStatusSuccess); + }; + + if(isForward) + { + test_set_tensor_descriptor(miopenTensorSoftmaxX, xTensor.desc); + test_set_tensor_descriptor(miopenTensorSoftmaxY, yTensor.desc); + } + else + { + test_set_tensor_descriptor(miopenTensorSoftmaxY, yTensor.desc); + test_set_tensor_descriptor(miopenTensorSoftmaxDY, dyTensor.desc); + test_set_tensor_descriptor(miopenTensorSoftmaxDX, dxTensor.desc); + } + + std::cerr << "Created softmax tensor descriptors." << std::endl; + } + + std::vector TestFindSolutions(Handle& handle) + { + std::cerr << "Testing miopenFindSolutions..." << std::endl; + + auto solutions = std::vector{}; + std::size_t found; + + // We expect to get only 1 or 2 solutions for softmax for now. Hardcode value 16 as just big + // enough value + solutions.resize(16); + + EXPECT_EQUAL(miopenFindSolutions( + &handle, problem, nullptr, solutions.data(), &found, solutions.size()), + miopenStatusSuccess); + EXPECT_TRUE(found > 0); + + solutions.resize(found); + + std::cerr << "Finished testing miopenFindSolutions." << std::endl; + return solutions; + } + + void TestSolutionAttributes(const std::vector& solutions) + { + std::cerr << "Testing miopenGetSolution..." << std::endl; + + for(const auto& solution : solutions) + { + float time; + std::size_t workspace_size; + uint64_t solver_id; + + EXPECT_EQUAL(miopenGetSolutionTime(solution, &time), miopenStatusSuccess); + EXPECT_EQUAL(miopenGetSolutionWorkspaceSize(solution, &workspace_size), + miopenStatusSuccess); + EXPECT_EQUAL(miopenGetSolutionSolverId(solution, &solver_id), miopenStatusSuccess); + } + + std::cerr << "Finished testing miopenGetSolution." << std::endl; + } + + void TestRunSolutionsForward(Handle& handle, const std::vector& solutions) + { + std::cerr << "Testing solution functions..." << std::endl; + + miopenTensorDescriptor_t x_desc = &xTensor.desc, y_desc = &yTensor.desc; + + const unsigned int numTensors = 2; + + for(const auto& solution : solutions) + { + auto arguments = std::make_unique(numTensors); + + auto in_gpu = handle.Write(xTensor.data); + auto out_gpu = handle.Write(yTensor.data); + + miopenTensorArgumentId_t names[numTensors] = {miopenTensorSoftmaxX, + miopenTensorSoftmaxY}; + void* buffers[numTensors] = {in_gpu.get(), out_gpu.get()}; + miopenTensorDescriptor_t descriptors[numTensors] = {x_desc, y_desc}; + + for(auto i = 0; i < numTensors; ++i) + { + arguments[i].id = names[i]; + arguments[i].descriptor = &descriptors[i]; + arguments[i].buffer = buffers[i]; + } + + std::cerr << "Run a solution." << std::endl; + EXPECT_EQUAL( + miopenRunSolution(&handle, solution, numTensors, arguments.get(), nullptr, 0), + miopenStatusSuccess); + + float alpha = softmax_descriptor.GetAlpha(); + float beta = softmax_descriptor.GetBeta(); + + // tensor yTensorDup = yTensor; + tensor yTensorRef = tensor{test_n, test_c, test_h, test_w}; + + auto out_gpu_ref = handle.Write(yTensorRef.data); + + // Run softmax in a usual way (which is tested) and compare results + EXPECT_EQUAL(miopenSoftmaxForward_V2(&handle, + &alpha, + x_desc, + in_gpu.get(), + &beta, + &yTensorRef.desc, + out_gpu_ref.get(), + softmax_descriptor.GetAlgorithm(), + softmax_descriptor.GetMode()), + miopenStatusSuccess); + + yTensor.data = handle.Read(out_gpu, yTensor.data.size()); + yTensorRef.data = handle.Read(out_gpu_ref, yTensorRef.data.size()); + + double error = miopen::rms_range(yTensorRef.data, yTensor.data); + const double tolerance = 1e-3; + + EXPECT_TRUE(std::isfinite(error) && error <= tolerance) + << "Outputs do not match each other. Error:" << error; + } + + std::cerr << "Finished testing solution functions." << std::endl; + } + + void TestRunSolutionsBackward(Handle& handle, const std::vector& solutions) + { + std::cerr << "Testing solution functions..." << std::endl; + + miopenTensorDescriptor_t y_desc = &yTensor.desc; + miopenTensorDescriptor_t dy_desc = &dyTensor.desc; + miopenTensorDescriptor_t dx_desc = &dxTensor.desc; + + const unsigned int numTensors = 3; + + for(const auto& solution : solutions) + { + auto arguments = std::make_unique(numTensors); + + auto in1_gpu = handle.Write(yTensor.data); + auto in2_gpu = handle.Write(dyTensor.data); + auto out_gpu = handle.Write(dxTensor.data); + + miopenTensorArgumentId_t names[numTensors] = { + miopenTensorSoftmaxY, miopenTensorSoftmaxDY, miopenTensorSoftmaxDX}; + void* buffers[numTensors] = {in1_gpu.get(), in2_gpu.get(), out_gpu.get()}; + miopenTensorDescriptor_t descriptors[numTensors] = {y_desc, dy_desc, dx_desc}; + + for(auto i = 0; i < numTensors; ++i) + { + arguments[i].id = names[i]; + arguments[i].descriptor = &descriptors[i]; + arguments[i].buffer = buffers[i]; + } + + std::cerr << "Run a solution." << std::endl; + EXPECT_EQUAL( + miopenRunSolution(&handle, solution, numTensors, arguments.get(), nullptr, 0), + miopenStatusSuccess); + + float alpha = softmax_descriptor.GetAlpha(); + float beta = softmax_descriptor.GetBeta(); + + // tensor yTensorDup = yTensor; + tensor dxTensorRef = tensor{test_n, test_c, test_h, test_w}; + + // this is dx + auto out_gpu_ref = handle.Write(dxTensorRef.data); + + // Run softmax in a usual way (which is tested) and compare results + EXPECT_EQUAL(miopenSoftmaxBackward_V2(&handle, + &alpha, + y_desc, + in1_gpu.get(), + dy_desc, + in2_gpu.get(), + &beta, + &dxTensorRef.desc, + out_gpu_ref.get(), + softmax_descriptor.GetAlgorithm(), + softmax_descriptor.GetMode()), + miopenStatusSuccess); + + yTensor.data = handle.Read(out_gpu, yTensor.data.size()); + dxTensorRef.data = handle.Read(out_gpu_ref, dxTensorRef.data.size()); + + double error = miopen::rms_range(dxTensorRef.data, yTensor.data); + const double tolerance = 1e-3; + + EXPECT_TRUE(std::isfinite(error) && error <= tolerance) + << "Outputs do not match each other. Error:" << error; + } + + std::cerr << "Finished testing solution functions." << std::endl; + } + + void Finalize() { EXPECT_EQUAL(miopenDestroyProblem(problem), miopenStatusSuccess); } + +private: + void Initialize() + { + softmax_descriptor.SetParams( + 1.0f, 0.0f, MIOPEN_SOFTMAX_ACCURATE, MIOPEN_SOFTMAX_MODE_CHANNEL); + + if(isForward) + { + xTensor = + tensor{test_n, test_c, test_h, test_w}.generate(tensor_elem_gen_integer{17}); + yTensor = tensor{test_n, test_c, test_h, test_w}; + + EXPECT_EQUAL(miopenCreateSoftmaxProblem( + &problem, &softmax_descriptor, miopenProblemDirectionForward), + miopenStatusSuccess); + } + else + { + yTensor = + tensor{test_n, test_c, test_h, test_w}.generate(tensor_elem_gen_integer{17}); + dyTensor = + tensor{test_n, test_c, test_h, test_w}.generate(tensor_elem_gen_integer{17}); + dxTensor = tensor{test_n, test_c, test_h, test_w}; + + EXPECT_EQUAL(miopenCreateSoftmaxProblem( + &problem, &softmax_descriptor, miopenProblemDirectionBackward), + miopenStatusSuccess); + } + + AddTensorDescriptors(); + } + +private: + tensor xTensor; + tensor yTensor; + + tensor dxTensor; + tensor dyTensor; + + SoftmaxDescriptor softmax_descriptor; + miopenProblem_t problem; + + bool isForward; + + const unsigned int test_n = 100; + const unsigned int test_c = 3; + const unsigned int test_h = 32; + const unsigned int test_w = 32; +}; + +TEST(TestSoftmaxFind20, softmaxForward) +{ + Handle& handle = get_handle(); + + SoftmaxFind20Test test(true); + + std::vector solutions = test.TestFindSolutions(handle); + test.TestSolutionAttributes(solutions); + + test.TestRunSolutionsForward(handle, solutions); + test.Finalize(); +} + +TEST(TestSoftmaxFind20, softmaxBackward) +{ + Handle& handle = get_handle(); + + SoftmaxFind20Test test(false); + + std::vector solutions = test.TestFindSolutions(handle); + test.TestSolutionAttributes(solutions); + + test.TestRunSolutionsBackward(handle, solutions); + test.Finalize(); +} diff --git a/test/gtest/solver_f8.hpp b/test/gtest/solver_f8.hpp index bf6fccc163..a4d311929c 100644 --- a/test/gtest/solver_f8.hpp +++ b/test/gtest/solver_f8.hpp @@ -26,7 +26,6 @@ #pragma once #include -#include #include "cpu_conv.hpp" #include "get_handle.hpp" #include "tensor_util.hpp" @@ -35,16 +34,11 @@ #include "conv_common.hpp" #include "hip_float8.hpp" #include "verify.hpp" +#include "../random.hpp" #include "conv_test_base.hpp" #include "f8_cast_util.hpp" -float scalar_gen_random_float(float low, float high) -{ - float r = static_cast(rand()) / static_cast(RAND_MAX) * (high + low) - low; - return r; -} - struct ConvTestCaseF8 { size_t N; @@ -91,8 +85,7 @@ struct ConvFwdSolverTestF8 weights = tensor{conv_config.k, conv_config.C, conv_config.y, conv_config.x}; auto gen_fp8_value = [=](auto...) { - const auto tmp = float8(scalar_gen_random_float(-0.5, 0.5)); - return tmp; + return prng::gen_A_to_B(static_cast(-0.5), static_cast(0.5)); }; input.generate(gen_fp8_value); diff --git a/test/gtest/sum.hpp b/test/gtest/sum.hpp index 518d562350..6e327efafc 100644 --- a/test/gtest/sum.hpp +++ b/test/gtest/sum.hpp @@ -99,15 +99,6 @@ std::vector SumTestConfigs() // clang-format on } -static int32_t SetTensorLayout(miopen::TensorDescriptor& desc) -{ - const std::vector& lens = desc.GetLengths(); - std::vector int32_t_lens(lens.begin(), lens.end()); - - // set the strides for the tensor - return SetTensorNd(&desc, int32_t_lens, desc.GetType()); -} - template struct SumTest : public ::testing::TestWithParam { @@ -135,10 +126,7 @@ struct SumTest : public ::testing::TestWithParam } } - SetTensorLayout(input.desc); - output = tensor{out_dims}; - SetTensorLayout(output.desc); std::fill(output.begin(), output.end(), std::numeric_limits::quiet_NaN()); ref_output = tensor{out_dims}; diff --git a/test/gtest/tuna_net.cpp b/test/gtest/tuna_net.cpp index 24c744268e..652fc0d9eb 100644 --- a/test/gtest/tuna_net.cpp +++ b/test/gtest/tuna_net.cpp @@ -6,33 +6,67 @@ struct TunaNetTestCase : AIModelTestCase { std::size_t expected_solver; + std::string device_architecture; }; std::vector GetGfx908FloatTestCases() { - return {{{{5, 256, 267, 300, 64, 1, 1, 0, 0, 1, 1, 1, 1, miopenConvolution}, + return {{{{1, 5, 256, 64, {267, 300}, {1, 1}, {0, 0}, {1, 1}, {1, 1}}, miopen::conv::Direction::Forward, miopenFloat, miopenTensorNCHW}, - 4}}; + 4, + "gfx908"}}; } std::vector GetGfx908HalfTestCases() { - return {{{{16, 256, 20, 84, 512, 5, 5, 1, 1, 1, 1, 1, 1, miopenConvolution}, + return {{{{1, 16, 256, 512, {20, 84}, {5, 5}, {1, 1}, {1, 1}, {1, 1}}, miopen::conv::Direction::Forward, miopenHalf, miopenTensorNCHW}, - 3}}; + 3, + "gfx908"}}; } std::vector GetGfx908BF16TestCases() { - return {{{{32, 1024, 15, 15, 512, 1, 1, 0, 0, 1, 1, 1, 1, miopenConvolution}, + return {{{{1, 32, 1024, 512, {15, 15}, {1, 1}, {0, 0}, {1, 1}, {1, 1}}, miopen::conv::Direction::Forward, miopenBFloat16, miopenTensorNCHW}, - 4}}; + 4, + "gfx908"}}; +} + +std::vector GetGfx90aFloatTestCases() +{ + return {{{{1, 5, 3, 64, {1301, 1333}, {7, 7}, {3, 3}, {2, 2}, {1, 1}}, + miopen::conv::Direction::Forward, + miopenFloat, + miopenTensorNCHW}, + 6, + "gfx90a"}}; +} + +std::vector GetGfx90aHalfTestCases() +{ + return {{{{1, 24, 1024, 2048, {14, 14}, {1, 1}, {0, 0}, {2, 2}, {1, 1}}, + miopen::conv::Direction::Forward, + miopenHalf, + miopenTensorNCHW}, + 4, + "gfx90a"}}; +} + +std::vector GetGfx90aBF16TestCases() +{ + return {{{{1, 2, 480, 192, {28, 28}, {1, 1}, {0, 0}, {1, 1}, {1, 1}}, + miopen::conv::Direction::Forward, + miopenBFloat16, + miopenTensorNCHW}, + 6, + "gfx90a"}}; } template @@ -61,13 +95,15 @@ struct TunaNetTest : public ::testing::TestWithParam conv_desc, test_case.direction); - expected_solver = test_case.expected_solver; + expected_solver = test_case.expected_solver; + device_architecture = test_case.device_architecture; #else GTEST_SKIP(); #endif } miopen::conv::ProblemDescription problem; std::size_t expected_solver; + std::string device_architecture; }; struct TunaNetTestFloat : TunaNetTest @@ -83,12 +119,13 @@ struct TunaNetTestBF16 : TunaNetTest }; void TestSolverPredictionModel(miopen::conv::ProblemDescription& problem, - std::size_t expected_solver) + std::size_t expected_solver, + std::string device_architecture) { #if MIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK auto&& handle = get_handle(); std::string device = handle.GetDeviceName(); - if(device != "gfx908") + if(device != device_architecture) GTEST_SKIP(); miopen::ExecutionContext ctx; ctx.SetStream(&handle); @@ -101,26 +138,27 @@ void TestSolverPredictionModel(miopen::conv::ProblemDescription& problem, #else std::ignore = problem; std::ignore = expected_solver; + std::ignore = device_architecture; GTEST_SKIP(); #endif } -TEST_P(TunaNetTestFloat, Gfx908TestSolverPredictionModelFloat) +TEST_P(TunaNetTestFloat, TestSolverPredictionModelFloat) { - TestSolverPredictionModel(problem, expected_solver); + TestSolverPredictionModel(problem, expected_solver, device_architecture); } -TEST_P(TunaNetTestHalf, Gfx908TestSolverPredictionModelHalf) +TEST_P(TunaNetTestHalf, TestSolverPredictionModelHalf) { - TestSolverPredictionModel(problem, expected_solver); + TestSolverPredictionModel(problem, expected_solver, device_architecture); } -TEST_P(TunaNetTestBF16, Gfx908TestSolverPredictionModelBF16) +TEST_P(TunaNetTestBF16, TestSolverPredictionModelBF16) { - TestSolverPredictionModel(problem, expected_solver); + TestSolverPredictionModel(problem, expected_solver, device_architecture); } -INSTANTIATE_TEST_SUITE_P(Gfx908TestSolverPredictionModelFloatTest, +INSTANTIATE_TEST_SUITE_P(Gfx908TestSolverPredictionModelFloat, TunaNetTestFloat, testing::ValuesIn(GetGfx908FloatTestCases())); @@ -131,3 +169,15 @@ INSTANTIATE_TEST_SUITE_P(Gfx908TestSolverPredictionModelHalfTest, INSTANTIATE_TEST_SUITE_P(Gfx908TestSolverPredictionModelBF16Test, TunaNetTestBF16, testing::ValuesIn(GetGfx908BF16TestCases())); + +INSTANTIATE_TEST_SUITE_P(Gfx90aTestSolverPredictionModelFloat, + TunaNetTestFloat, + testing::ValuesIn(GetGfx90aFloatTestCases())); + +INSTANTIATE_TEST_SUITE_P(Gfx90aTestSolverPredictionModelHalfTest, + TunaNetTestHalf, + testing::ValuesIn(GetGfx90aHalfTestCases())); + +INSTANTIATE_TEST_SUITE_P(Gfx90aTestSolverPredictionModelBF16Test, + TunaNetTestBF16, + testing::ValuesIn(GetGfx90aBF16TestCases())); diff --git a/test/include_inliner.cpp b/test/include_inliner.cpp index 6583515cbb..62e6510d3f 100644 --- a/test/include_inliner.cpp +++ b/test/include_inliner.cpp @@ -37,23 +37,24 @@ namespace miopen { namespace tests { -static int Child(std::string_view cmd, const fs::path& path) -{ - return miopen::Process{cmd}("-source " + path.string()); -} - class InlinerTest { + const TmpDir test_srcs{"test_include_inliner"}; + + int Child(const fs::path& exe, const fs::path& source) const + { + return test_srcs.Execute(exe.string(), "-source " + source); + } + public: void Run(const fs::path& exe_path) const { - const TmpDir test_srcs{"test_include_inliner"}; - const auto addkernels = (exe_path.parent_path() / "addkernels").string(); + const auto addkernels = make_executable_name(exe_path.parent_path() / "addkernels"); const auto header_filename = "header.h"; - const auto asm_src = test_srcs.path / "valid.s"; - const auto valid_src = test_srcs.path / "valid.cl"; - const auto invalid_src = test_srcs.path / "invalid.cl"; - const auto header_src = test_srcs.path / header_filename; + const auto asm_src = test_srcs / "valid.s"; + const auto valid_src = test_srcs / "valid.cl"; + const auto invalid_src = test_srcs / "invalid.cl"; + const auto header_src = test_srcs / header_filename; // clang-format-off std::ofstream(valid_src.c_str()) << "#include <" << header_filename << ">\n" diff --git a/test/lstm_common.hpp b/test/lstm_common.hpp index d2b7d1a077..85d17dc138 100644 --- a/test/lstm_common.hpp +++ b/test/lstm_common.hpp @@ -465,7 +465,7 @@ struct verify_forward_infer_lstm : verify_forward_lstm { reserveSpaceSize /= sizeof(T); reserveSpaceSize -= - nLayers * std::accumulate(batch_seq.begin(), batch_seq.begin() + seqLength, 0) * + nLayers * std::accumulate(batch_seq.begin(), batch_seq.begin() + seqLength, 0ULL) * hiddenSize * bi; reserveSpaceSize *= 2; reserveSpaceSize *= sizeof(T); @@ -771,7 +771,7 @@ struct verify_forward_train_lstm : verify_forward_lstm outputCPPDescs, outputDescs, batch_seq, out_h, miopen::deref(rnnDesc).dataType); size_t inputBatchLenSum = - std::accumulate(batch_seq.begin(), batch_seq.begin() + seqLength, 0); + std::accumulate(batch_seq.begin(), batch_seq.begin() + seqLength, 0ULL); size_t reserveSpaceSize; reserveSpaceSize = 2 * 6 * miopen::deref(rnnDesc).nLayers * inputBatchLenSum * out_h; @@ -1075,7 +1075,8 @@ verify_backward_data_lstm::cpu() const std::vector dhx(initHidden.size()); std::vector dcx(initHidden.size()); - size_t inputBatchLenSum = std::accumulate(batch_seq.begin(), batch_seq.begin() + seqLength, 0); + size_t inputBatchLenSum = + std::accumulate(batch_seq.begin(), batch_seq.begin() + seqLength, 0ULL); size_t reserveSpaceSize; reserveSpaceSize = 2ULL * 6 * miopen::deref(rnnDesc).nLayers * inputBatchLenSum * hiddenSize * ((dirMode != 0) ? 2 : 1); @@ -1778,7 +1779,7 @@ struct lstm_basic_driver : test_driver std::vector rsvgpu(reserveSpaceSize, T(0)); size_t inputBatchLenSum = - std::accumulate(batchSeq.begin(), batchSeq.begin() + seqLength, 0); + std::accumulate(batchSeq.begin(), batchSeq.begin() + seqLength, 0ULL); reserveSpaceSize = 2ULL * 6 * numLayers * inputBatchLenSum * hiddenSize * ((dirMode != 0) ? 2 : 1); if(useDropout != 0) diff --git a/test/perfdb.cpp b/test/perfdb.cpp index 591539fae4..6928125ba9 100644 --- a/test/perfdb.cpp +++ b/test/perfdb.cpp @@ -151,15 +151,22 @@ struct TestData return {static_cast(rnd.Next()), static_cast(rnd.Next())}; } + template + static void VisitAll(TSelf&& self, Visitor visitor) + { + visitor(self.x, "x"); + visitor(self.y, "y"); + } + void Serialize(std::ostream& s) const { - static const auto sep = ','; + static const auto sep = 'x'; s << x << sep << y; } bool Deserialize(const std::string& s) { - static const auto sep = ','; + static const auto sep = 'x'; TestData t(NoInit{}); std::istringstream ss(s); @@ -282,7 +289,7 @@ class DbTest const std::array, count> values) { std::ostringstream ss_vals; - ss_vals << key.x << ',' << key.y << '='; + ss_vals << key.x << 'x' << key.y << '='; auto first = true; @@ -292,7 +299,7 @@ class DbTest ss_vals << ";"; first = false; - ss_vals << id_value.first << ':' << id_value.second.x << ',' << id_value.second.y; + ss_vals << id_value.first << ':' << id_value.second.x << 'x' << id_value.second.y; } std::ofstream(db_path, std::ios::out | std::ios::ate) << ss_vals.str() << std::endl; @@ -330,7 +337,7 @@ class DbFindTest : public DbTest RawWrite(temp_file, key(), common_data()); - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); ValidateSingleEntry(key(), common_data(), db); const TestData invalid_key(100, 200); @@ -352,12 +359,12 @@ class DbStoreTest : public DbTest "Testing " << ArgsHelper::db_class::Get() << " for reading stored data..."); - DbRecord record(key()); + DbRecord record(DbKinds::PerfDb, key()); EXPECT(record.SetValues(id0(), value0())); EXPECT(record.SetValues(id1(), value1())); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.StoreRecord(record)); } @@ -365,7 +372,7 @@ class DbStoreTest : public DbTest std::string read; EXPECT(std::getline(std::ifstream(temp_file), read).good()); - TDb db{temp_file}; + TDb db{DbKinds::PerfDb, temp_file}; ValidateSingleEntry(key(), common_data(), db); } }; @@ -384,21 +391,21 @@ class DbUpdateTest : public DbTest << " for updating existing records..."); // Store record0 (key=id0:value0) - DbRecord record0(key()); + DbRecord record0(DbKinds::PerfDb, key()); EXPECT(record0.SetValues(id0(), value0())); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.StoreRecord(record0)); } // Update with record1 (key=id1:value1) - DbRecord record1(key()); + DbRecord record1(DbKinds::PerfDb, key()); EXPECT(record1.SetValues(id1(), value1())); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.UpdateRecord(record1)); } @@ -411,7 +418,7 @@ class DbUpdateTest : public DbTest EXPECT_EQUAL(value1(), read1); // Check record that is stored in db (key=id0:value0;id1:value1) - TDb db{temp_file}; + TDb db{DbKinds::PerfDb, temp_file}; ValidateSingleEntry(key(), common_data(), db); } }; @@ -430,18 +437,18 @@ class DbRemoveTest : public DbTest "Testing " << ArgsHelper::db_class::Get() << " for removing records..."); - DbRecord record(key()); + DbRecord record(DbKinds::PerfDb, key()); EXPECT(record.SetValues(id0(), value0())); EXPECT(record.SetValues(id1(), value1())); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.StoreRecord(record)); } { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.FindRecord(key())); EXPECT(db.RemoveRecord(key())); @@ -465,7 +472,7 @@ class DbReadTest : public DbTest << " for reading premade file by Load..."); RawWrite(temp_file, key(), common_data()); - TDb db{temp_file}; + TDb db{DbKinds::PerfDb, temp_file}; ValidateSingleEntry(key(), common_data(), db); } }; @@ -484,7 +491,7 @@ class DbWriteTest : public DbTest << " for storing unexistent records by update..."); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.Update(key(), id0(), value0())); EXPECT(db.Update(key(), id1(), value1())); @@ -493,7 +500,7 @@ class DbWriteTest : public DbTest std::string read; EXPECT(std::getline(std::ifstream(temp_file), read).good()); - TDb db{temp_file}; + TDb db{DbKinds::PerfDb, temp_file}; ValidateSingleEntry(key(), common_data(), db); } }; @@ -514,7 +521,7 @@ class DbOperationsTest : public DbTest const TestData to_be_rewritten(7, 8); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.Update(key(), id0(), to_be_rewritten)); EXPECT(db.Update(key(), id1(), to_be_rewritten)); @@ -528,7 +535,7 @@ class DbOperationsTest : public DbTest } { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); // Rewriting existing value to store it to file. EXPECT(db.Update(key(), id0(), value0())); @@ -537,7 +544,7 @@ class DbOperationsTest : public DbTest { TestData read0, read1, read_missing; const auto read_missing_cmp(read_missing); - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); // Loading by id not present in record should execute well but return false as nothing // was read. @@ -568,7 +575,7 @@ class DbOperationsTest : public DbTest { TestData read0, read1; const auto read_missing_cmp(read0); - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(!db.Load(key(), id0(), read0)); EXPECT(db.Load(key(), id1(), read1)); @@ -594,13 +601,13 @@ class DbParallelTest : public DbTest << " for using two objects targeting one file existing in one scope..."); { - TDb db(temp_file); + TDb db(DbKinds::PerfDb, temp_file); EXPECT(db.Update(key(), id0(), value0())); } { - TDb db0(temp_file); - TDb db1(temp_file); + TDb db0(DbKinds::PerfDb, temp_file); + TDb db1(DbKinds::PerfDb, temp_file); auto r0 = db0.FindRecord(key()); auto r1 = db1.FindRecord(key()); @@ -621,7 +628,7 @@ class DbParallelTest : public DbTest {id2(), value2()}, }}; - TDb db{temp_file}; + TDb db{DbKinds::PerfDb, temp_file}; ValidateSingleEntry(key(), data, db); } }; @@ -883,7 +890,7 @@ class DbMultiThreadedTest : public DbTest MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Launching test threads..."); threads.reserve(DBMultiThreadedTestWork::threads_count); const std::string p = temp_file; - const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(p, false)); + const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(DbKinds::PerfDb, p, false)); { std::unique_lock lock(mutex); @@ -927,7 +934,7 @@ class DbMultiThreadedReadTest : public DbTest MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Initializing test data..."); const std::string p = temp_file; - const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(p, false)); + const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(DbKinds::PerfDb, p, false)); DBMultiThreadedTestWork::FillForReading(c); MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Launching test threads..."); @@ -1008,7 +1015,7 @@ class DbMultiProcessTest : public DbTest fs::remove(lock_file_path); const std::string p = temp_file; - const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(p, false)); + const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(DbKinds::PerfDb, p, false)); MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Validating results..."); DBMultiThreadedTestWork::ValidateCommonPart(c); @@ -1022,7 +1029,8 @@ class DbMultiProcessTest : public DbTest std::lock_guard lock(file_lock); } - const auto c = [&db_path]() MIOPEN_RETURNS(GetDbInstance(db_path, false)); + const auto c = [&db_path]() + MIOPEN_RETURNS(GetDbInstance(DbKinds::PerfDb, db_path, false)); if(write) DBMultiThreadedTestWork::WorkItem(id, c, "mp"); @@ -1052,7 +1060,7 @@ class DbMultiProcessReadTest : public DbTest MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Initializing test data..."); std::string p = temp_file; - const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(p, false)); + const auto c = [&p]() MIOPEN_RETURNS(GetDbInstance(DbKinds::PerfDb, p, false)); DBMultiThreadedTestWork::FillForReading(c); MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Launching test processes..."); @@ -1078,7 +1086,7 @@ class DbMultiProcessReadTest : public DbTest if(full_set()) args += " --all"; - MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", exe_path().string() + " " + args); + MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", exe_path() + " " + args); children.emplace_back(exe_path(), args); } // clang-format on @@ -1172,7 +1180,8 @@ class DbMultiFileReadTest : public DbMultiFileTest {id0(), value2()}, }}; - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db( + DbKinds::PerfDb, temp_file, user_db_path); if(merge_records) ValidateSingleEntry(key(), merged_data, db); else @@ -1186,14 +1195,16 @@ class DbMultiFileReadTest : public DbMultiFileTest void ReadUser() const { RawWrite(user_db_path, key(), single_item_data()); - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db( + DbKinds::PerfDb, temp_file, user_db_path); ValidateSingleEntry(key(), single_item_data(), db); } void ReadInstalled() const { RawWrite(temp_file, key(), single_item_data()); - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db( + DbKinds::PerfDb, temp_file, user_db_path); ValidateSingleEntry(key(), single_item_data(), db); } @@ -1213,12 +1224,12 @@ class DbMultiFileWriteTest : public DbMultiFileTest { MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Running multifile write test..."); - DbRecord record(key()); + DbRecord record(DbKinds::PerfDb, key()); EXPECT(record.SetValues(id0(), value0())); EXPECT(record.SetValues(id1(), value1())); { - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.StoreRecord(record)); } @@ -1227,7 +1238,7 @@ class DbMultiFileWriteTest : public DbMultiFileTest EXPECT(!std::getline(std::ifstream(temp_file), read).good()); EXPECT(std::getline(std::ifstream(user_db_path), read).good()); - auto db = MultiFileDb{temp_file, user_db_path}; + auto db = MultiFileDb{DbKinds::PerfDb, temp_file, user_db_path}; ValidateSingleEntry(key(), common_data(), db); } }; @@ -1251,11 +1262,11 @@ class DbMultiFileOperationsTest : public DbMultiFileTest MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Running multifile operations test..."); { - DbRecord record(key()); + DbRecord record(DbKinds::PerfDb, key()); EXPECT(record.SetValues(id0(), value0())); EXPECT(record.SetValues(id1(), value2())); - PlainTextDb db(temp_file); + PlainTextDb db(DbKinds::PerfDb, temp_file); EXPECT(db.StoreRecord(record)); } } @@ -1265,12 +1276,12 @@ class DbMultiFileOperationsTest : public DbMultiFileTest MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Update test..."); { - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.Update(key(), id1(), value1())); } { - PlainTextDb db(user_db_path); + PlainTextDb db(DbKinds::PerfDb, user_db_path); TestData read(TestData::NoInit{}); EXPECT(!db.Load(key(), id0(), read)); EXPECT(db.Load(key(), id1(), read)); @@ -1278,7 +1289,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest } { - PlainTextDb db(temp_file); + PlainTextDb db(DbKinds::PerfDb, temp_file); ValidateData(db, value2()); } } @@ -1287,7 +1298,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest { MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Load test..."); - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); ValidateData(db, value1()); } @@ -1295,7 +1306,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest { MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Remove test..."); - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); EXPECT(!db.Remove(key(), id0())); EXPECT(db.Remove(key(), id1())); @@ -1306,7 +1317,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest { MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Remove record test..."); - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.Update(key(), id1(), value1())); EXPECT(db.RemoveRecord(key())); @@ -1340,7 +1351,9 @@ class DbMultiFileMultiThreadedReadTest : public DbMultiFileTest MIOPEN_LOG_CUSTOM(LoggingLevel::Default, "Test", "Initializing test data..."); const std::string p = temp_file; const auto& up = user_db_path; - const auto c = [&p, up]() { return MultiFileDb(p, up); }; + const auto c = [&p, up]() { + return MultiFileDb(DbKinds::PerfDb, p, up); + }; ResetDb(); DBMultiThreadedTestWork::FillForReading(c); @@ -1388,7 +1401,9 @@ class DbMultiFileMultiThreadedTest : public DbMultiFileTest threads.reserve(DBMultiThreadedTestWork::threads_count); const std::string p = temp_file; const auto up = user_db_path; - const auto c = [&p, &up]() { return MultiFileDb(p, up); }; + const auto c = [&p, &up]() { + return MultiFileDb(DbKinds::PerfDb, p, up); + }; { std::unique_lock lock(mutex); diff --git a/test/pooling_common.hpp b/test/pooling_common.hpp index 33bcb7164f..231b635a63 100644 --- a/test/pooling_common.hpp +++ b/test/pooling_common.hpp @@ -283,7 +283,7 @@ struct verify_backward_pooling std::size_t mx_idx_dim = mx_idx; mx_idx_dim /= std::accumulate(in_dim.begin() + i + 3, in_dim.end(), - 1, + 1ULL, std::multiplies()); mx_idx_dim %= in_dim[i + 2]; idx[i + 2] = mx_idx_dim; diff --git a/test/reduce_test.cpp b/test/reduce_test.cpp index dc92a20318..d5668b09af 100644 --- a/test/reduce_test.cpp +++ b/test/reduce_test.cpp @@ -23,930 +23,7 @@ * SOFTWARE. * *******************************************************************************/ -#include -#include "driver.hpp" -#include "test.hpp" -#include "verify.hpp" -#include "workspace.hpp" -#include "get_handle.hpp" -#include "tensor_holder.hpp" -#include "random.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cpu_reduce_util.hpp" - -template -struct verify_reduce_with_indices -{ - miopen::ReduceTensorDescriptor reduce; - tensor input; - tensor output; - tensor workspace; - tensor indices; - float alpha; - float beta; - - miopenReduceTensorOp_t reduceOp; - miopenDataType_t compTypeVal; - miopenNanPropagation_t nanOpt; - miopenReduceTensorIndices_t indicesOpt; - miopenIndicesType_t indicesType; - - verify_reduce_with_indices(const miopen::ReduceTensorDescriptor& reduce_, - const tensor& input_, - const tensor& output_, - const tensor& workspace_, - const tensor& indices_, - float alpha_, - float beta_) - : reduce(reduce_), - input(input_), - output(output_), - workspace(workspace_), - indices(indices_), - alpha(alpha_), - beta(beta_), - reduceOp(reduce.reduceTensorOp_), - compTypeVal(reduce.reduceTensorCompType_), - nanOpt(reduce.reduceTensorNanOpt_), - indicesOpt(reduce.reduceTensorIndices_), - indicesType(reduce.reduceTensorIndicesType_) - { - } - - tensor cpu() const - { - using reduce::convert_type; - - std::tuple, tensor> results; - - if(compTypeVal == miopenFloat) - { - if(std::is_same::value) - results = cpuImpl(); - else - results = cpuImpl(); - } - else if(compTypeVal == miopenHalf) - { - if(std::is_same::value) - results = cpuImpl(); - else if(std::is_same::value) - results = cpuImpl(); - else - results = cpuImpl(); - } - else if(compTypeVal == miopenDouble) - results = cpuImpl(); - - if(toVerifyData) - { - const auto& dimLengths = output.desc.GetLengths(); - - auto result_dataFloat = tensor(dimLengths); - - auto& result_dataT = std::get<0>(results); - - for(size_t i = 0; i < result_dataT.data.size(); i++) - result_dataFloat.data[i] = convert_type(result_dataT.data[i]); - - return (result_dataFloat); - } - else - { - const auto& dimLengths = indices.desc.GetLengths(); - - auto result_indicesFloat = tensor(dimLengths); - - auto& result_indices = std::get<1>(results); - - for(size_t i = 0; i < result_indices.data.size(); i++) - result_indicesFloat.data[i] = static_cast(result_indices.data[i]); - - return (result_indicesFloat); - }; - }; - - tensor gpu() const - { - using reduce::convert_type; - - std::tuple, tensor> results; - - results = gpuImpl(); - - if(toVerifyData) - { - const auto& dimLengths = output.desc.GetLengths(); - - auto result_dataFloat = tensor(dimLengths); - - tensor& result_dataT = std::get<0>(results); - - for(size_t i = 0; i < result_dataT.data.size(); i++) - result_dataFloat.data[i] = convert_type(result_dataT.data[i]); - - return (result_dataFloat); - } - else - { - const auto& dimLengths = indices.desc.GetLengths(); - - auto result_indicesFloat = tensor(dimLengths); - - tensor& result_indices = std::get<1>(results); - - for(size_t i = 0; i < result_indices.data.size(); i++) - result_indicesFloat.data[i] = static_cast(result_indices.data[i]); - - return (result_indicesFloat); - }; - }; - - template - std::tuple, tensor> cpuImpl() const - { - using reduce::binop_with_nan_check; - using reduce::binop_with_nan_check2; - using reduce::convert_type; - using reduce::float_equal_one; - using reduce::float_equal_zero; - using reduce::PosUnaryOpFn; - using reduce::PreUnaryOpFn; - using reduce::ReduceOpFn2; - using reduce::ReduceOpZeroVal; - - auto inLengths = input.desc.GetLengths(); - auto outLengths = output.desc.GetLengths(); - auto inStrides = input.desc.GetStrides(); - auto outStrides = output.desc.GetStrides(); - - // replicate - auto res = output; - auto res_indices = indices; - - std::vector invariantLengths; - std::vector toReduceLengths; - - std::vector invariantDims; - std::vector toReduceDims; - - for(int i = 0; i < inLengths.size(); i++) - { - if(inLengths[i] == outLengths[i]) - invariantDims.push_back(i); - else - toReduceDims.push_back(i); - } - - invariantLengths.resize(invariantDims.size()); - for(int i = 0; i < invariantDims.size(); i++) - invariantLengths[i] = inLengths[invariantDims[i]]; - - toReduceLengths.resize(toReduceDims.size()); - for(int i = 0; i < toReduceDims.size(); i++) - toReduceLengths[i] = inLengths[toReduceDims[i]]; - - bool reduceAllDims = invariantDims.empty(); - - auto opReduce = ReduceOpFn2(reduceOp); - - std::size_t divider = std::accumulate( - toReduceLengths.begin(), toReduceLengths.end(), std::size_t{1}, std::multiplies<>{}); - - auto PreUnaryOp = PreUnaryOpFn(reduceOp, divider); - - if(reduceAllDims) - { - std::vector> indexes_1; - - get_all_indexes(inLengths, 0, indexes_1); - - compType accuVal = ReduceOpZeroVal(reduceOp); - int accuIndex = 0; - - // go through indexes of the invariant dimensions - for(const auto& src_index : indexes_1) - { - auto src_offset = get_offset_from_index(inStrides, src_index); - - auto currVal = convert_type(input.data[src_offset]); - - // unary operation before reducing, only needed by AMAX. For MIN/MAX, nothing is - // actually done - PreUnaryOp(currVal); - - int currIndex = get_flatten_offset(inLengths, src_index); - binop_with_nan_check2(nanOpt, opReduce, accuVal, currVal, accuIndex, currIndex); - } - - // scale the accumulated value - if(!float_equal_one(alpha)) - accuVal *= convert_type(alpha); - - // scale the prior dst value and add it to the accumulated value - if(!float_equal_zero(beta)) - { - accuVal += convert_type(output.data[0]) * convert_type(beta); - } - - // store the reduced value to dst location - res.data[0] = convert_type(accuVal); - res_indices.data[0] = accuIndex; - } - else - { - std::vector> indexes_1, indexes_2; - - get_all_indexes(invariantLengths, 0, indexes_1); - get_all_indexes(toReduceLengths, 0, indexes_2); - - // go through indexes of the invariant dimensions - for(const auto& index_1 : indexes_1) - { - std::vector src_index; - std::vector dst_index; - - src_index.resize(inLengths.size()); - dst_index.resize(inLengths.size()); - - std::fill(dst_index.begin(), dst_index.end(), 0); - - for(int k = 0; k < invariantDims.size(); k++) - dst_index[invariantDims[k]] = index_1[k]; - - auto dst_offset = get_offset_from_index(outStrides, dst_index); - - // generate the part of the index belonging to the invariant dims - for(int k = 0; k < invariantDims.size(); k++) - src_index[invariantDims[k]] = index_1[k]; - - compType accuVal = ReduceOpZeroVal(reduceOp); - int accuIndex = 0; - - // go through indexes of the toReduce dimensions - for(const auto& index_2 : indexes_2) - { - // generate the part of the index belonging to the toReduce dims - for(int k = 0; k < toReduceDims.size(); k++) - src_index[toReduceDims[k]] = index_2[k]; - - auto src_offset = get_offset_from_index(inStrides, src_index); - - auto currVal = convert_type(input.data[src_offset]); - - // unary operation before reducing, only needed by AMAX. For MIN/MAX, nothing is - // actually done - PreUnaryOp(currVal); - - auto currIndex = get_flatten_offset(toReduceLengths, index_2); - binop_with_nan_check2(nanOpt, opReduce, accuVal, currVal, accuIndex, currIndex); - }; - - // scale the accumulated value - if(!float_equal_one(alpha)) - accuVal *= convert_type(alpha); - - // scale the prior dst value and add it to the accumulated value - if(!float_equal_zero(beta)) - { - accuVal += convert_type(output.data[dst_offset]) * - convert_type(beta); - } - - // store the reduced value to dst location - res.data[dst_offset] = convert_type(accuVal); - res_indices.data[dst_offset] = accuIndex; // store the index - }; - }; - - return (std::make_tuple(res, res_indices)); - } - - std::tuple, tensor> gpuImpl() const - { - auto&& handle = get_handle(); - auto input_dev = handle.Write(input.data); - auto output_dev = handle.Write(output.data); - - // replicate - auto res = output; - auto res_indices = indices; - - Workspace idxspace{}; - idxspace.Write(indices.data); - - Workspace wspace{}; - wspace.Write(workspace.data); - - const double alpha64 = alpha; - const double beta64 = beta; - - const void* const alphaPtr = (std::is_same::value) - ? static_cast(&alpha64) - : static_cast(&alpha); - const void* const betaPtr = (std::is_same::value) - ? static_cast(&beta64) - : static_cast(&beta); - - if(wspace.size() > 0) - { - reduce.ReduceTensor(get_handle(), - idxspace.ptr(), - idxspace.size(), - wspace.ptr(), - wspace.size(), - alphaPtr, - input.desc, - input_dev.get(), - betaPtr, - output.desc, - output_dev.get()); - } - else - { - reduce.ReduceTensor(get_handle(), - idxspace.ptr(), - idxspace.size(), - nullptr, - 0, - alphaPtr, - input.desc, - input_dev.get(), - betaPtr, - output.desc, - output_dev.get()); - }; - - res.data = handle.Read(output_dev, res.data.size()); - res_indices.data = idxspace.Read(); - - return (std::make_tuple(res, res_indices)); - } - - void fail(int) const - { - std::cout << "verify_reduce_with_indices failed" << std::endl; - std::cout << "Input Tensor" - << " " << input.desc.ToString() << std::endl; - } -}; - -template -struct verify_reduce_no_indices -{ - miopen::ReduceTensorDescriptor reduce; - tensor input; - tensor output; - tensor workspace; - float alpha; - float beta; - - miopenReduceTensorOp_t reduceOp; - miopenDataType_t compTypeVal; - miopenNanPropagation_t nanOpt; - - verify_reduce_no_indices( // NOLINT (hicpp-member-init) - const miopen::ReduceTensorDescriptor& reduce_, - const tensor& input_, - const tensor& output_, - const tensor& workspace_, - float alpha_, - float beta_) - { - reduce = reduce_; - input = input_; - output = output_; - workspace = workspace_; - alpha = alpha_; - beta = beta_; - - reduceOp = reduce.reduceTensorOp_; - compTypeVal = reduce.reduceTensorCompType_; - nanOpt = reduce.reduceTensorNanOpt_; - } - - tensor cpu() - { - using reduce::convert_type; - - tensor result; - - if(compTypeVal == miopenFloat) - { - if(std::is_same::value) - result = cpuImpl(); - else - result = cpuImpl(); - } - else if(compTypeVal == miopenHalf) - { - if(std::is_same::value) - result = cpuImpl(); - else if(std::is_same::value) - result = cpuImpl(); - else - result = cpuImpl(); - } - else if(compTypeVal == miopenDouble) - result = cpuImpl(); - - const auto& dimLengths = output.desc.GetLengths(); - auto result_dataFloat = tensor(dimLengths); - - for(size_t i = 0; i < result.data.size(); i++) - result_dataFloat.data[i] = convert_type(result.data[i]); - - return (result_dataFloat); - }; - - template - tensor cpuImpl() const - { - using reduce::binop_with_nan_check; - using reduce::binop_with_nan_check2; - using reduce::convert_type; - using reduce::float_equal_one; - using reduce::float_equal_zero; - using reduce::PosUnaryOpFn; - using reduce::PreUnaryOpFn; - using reduce::ReduceOpFn; - using reduce::ReduceOpZeroVal; - - auto inLengths = input.desc.GetLengths(); - auto outLengths = output.desc.GetLengths(); - auto inStrides = input.desc.GetStrides(); - auto outStrides = output.desc.GetStrides(); - - // replicate - auto res = output; - - std::vector invariantLengths; - std::vector toReduceLengths; - - std::vector invariantDims; - std::vector toReduceDims; - - for(int i = 0; i < inLengths.size(); i++) - { - if(inLengths[i] == outLengths[i]) - invariantDims.push_back(i); - else - toReduceDims.push_back(i); - } - - invariantLengths.resize(invariantDims.size()); - for(int i = 0; i < invariantDims.size(); i++) - invariantLengths[i] = inLengths[invariantDims[i]]; - - toReduceLengths.resize(toReduceDims.size()); - for(int i = 0; i < toReduceDims.size(); i++) - toReduceLengths[i] = inLengths[toReduceDims[i]]; - - bool reduceAllDims = invariantDims.empty(); - - auto opReduce = ReduceOpFn(reduceOp); - - std::size_t divider = std::accumulate( - toReduceLengths.begin(), toReduceLengths.end(), std::size_t{1}, std::multiplies<>{}); - - auto PreUnaryOp = PreUnaryOpFn(reduceOp, divider); - auto PosUnaryOp = PosUnaryOpFn(reduceOp, divider); - - if(reduceAllDims) - { - std::vector> indexes_1; - - get_all_indexes(inLengths, 0, indexes_1); - - compType accuVal = ReduceOpZeroVal(reduceOp); - - // go through indexes of the invariant dimensions - for(const auto& src_index : indexes_1) - { - auto src_offset = get_offset_from_index(inStrides, src_index); - - auto currVal = convert_type(input.data[src_offset]); - - PreUnaryOp(currVal); - - binop_with_nan_check(nanOpt, opReduce, accuVal, currVal); - }; - - PosUnaryOp(accuVal); - - // scale the accumulated value - if(!float_equal_one(alpha)) - accuVal *= convert_type(alpha); - - // scale the prior dst value and add it to the accumulated value - if(!float_equal_zero(beta)) - accuVal += convert_type(output.data[0]) * convert_type(beta); - - // store the reduced value to dst location - res.data[0] = convert_type(accuVal); - } - else - { - std::vector> indexes_1, indexes_2; - - get_all_indexes(invariantLengths, 0, indexes_1); - get_all_indexes(toReduceLengths, 0, indexes_2); - - // go through indexes of the invariant dimensions - for(const auto& index_1 : indexes_1) - { - std::vector src_index; - std::vector dst_index; - - src_index.resize(inLengths.size()); - dst_index.resize(inLengths.size()); - - std::fill(dst_index.begin(), dst_index.end(), 0); - - for(int k = 0; k < invariantDims.size(); k++) - dst_index[invariantDims[k]] = index_1[k]; - - auto dst_offset = get_offset_from_index(outStrides, dst_index); - - // generate the part of the index belonging to the invariant dims - for(int k = 0; k < invariantDims.size(); k++) - src_index[invariantDims[k]] = index_1[k]; - - compType accuVal = ReduceOpZeroVal(reduceOp); - - // go through indexes of the toReduce dimensions - for(const auto& index_2 : indexes_2) - { - // generate the part of the index belonging to the toReduce dims - for(int k = 0; k < toReduceDims.size(); k++) - src_index[toReduceDims[k]] = index_2[k]; - - auto src_offset = get_offset_from_index(inStrides, src_index); - - auto currVal = convert_type(input.data[src_offset]); - - PreUnaryOp(currVal); - - binop_with_nan_check(nanOpt, opReduce, accuVal, currVal); - }; - - PosUnaryOp(accuVal); - - // scale the accumulated value - if(!float_equal_one(alpha)) - accuVal *= convert_type(alpha); - - // scale the prior dst value and add it to the accumulated value - if(!float_equal_zero(beta)) - { - accuVal += convert_type(output.data[dst_offset]) * - convert_type(beta); - } - - // store the reduced value to dst location - res.data[dst_offset] = convert_type(accuVal); - }; - }; - - return (res); - } - - tensor gpu() const - { - using reduce::convert_type; - - auto result = gpuImpl(); - - const auto& dimLengths = output.desc.GetLengths(); - auto result_dataFloat = tensor(dimLengths); - - for(size_t i = 0; i < result.data.size(); i++) - result_dataFloat.data[i] = convert_type(result.data[i]); - - return (result_dataFloat); - }; - - tensor gpuImpl() const - { - auto&& handle = get_handle(); - auto input_dev = handle.Write(input.data); - auto output_dev = handle.Write(output.data); - - // replicate - auto res = output; - - Workspace wspace{}; - wspace.Write(workspace.data); - - const double alpha64 = alpha; - const double beta64 = beta; - - const void* const alphaPtr = (std::is_same::value) - ? static_cast(&alpha64) - : static_cast(&alpha); - const void* const betaPtr = (std::is_same::value) - ? static_cast(&beta64) - : static_cast(&beta); - - if(wspace.size() > 0) - { - reduce.ReduceTensor(get_handle(), - nullptr, - 0, - wspace.ptr(), - wspace.size(), - alphaPtr, - input.desc, - input_dev.get(), - betaPtr, - output.desc, - output_dev.get()); - } - else - { - reduce.ReduceTensor(get_handle(), - nullptr, - 0, - nullptr, - 0, - alphaPtr, - input.desc, - input_dev.get(), - betaPtr, - output.desc, - output_dev.get()); - }; - - res.data = handle.Read(output_dev, res.data.size()); - - return (res); - } - - void fail(int) const - { - std::cout << "verify_reduce_no_indices failed" << std::endl; - std::cout << "Input Tensor" - << " " << input.desc.ToString() << std::endl; - } -}; - -template -struct reduce_driver : test_driver -{ - int reduceOp = 0; // miopenReduceTensorOp_t reduceOp; - int compTypeVal = 1; // miopenDataType_t compTypeVal; - int nanOpt = 0; // miopenNanPropagation_t nanOpt; - int indicesOpt = 0; // miopenReduceTensorIndices_t indicesOpt; - miopenIndicesType_t indicesType = MIOPEN_32BIT_INDICES; - - std::vector inLengths; // the lengths of the input tensor's dimensions - std::vector - toReduceDims; // the indexes of the dimensions to be reduced in the input tensor - - std::vector scales; - float alpha = 1.0f; - float beta = 0.0f; - - std::vector> get_tensor_lengths() - { - if(std::is_same::value) - { - return { - {4, 3, 60, 50}, - }; - } - else - { - return { - {64, 3, 280, 81}, - }; - } - } - - std::vector> get_toreduce_dims() - { - std::vector> tensor_dims = { - {0}, {1}, {2}, {3}, {0, 1}, {0, 3}, {0, 2}, {2, 3}, {0, 1, 3}, {1, 2, 3}, {0, 1, 2, 3}}; - - return tensor_dims; - } - - reduce_driver() - { - add(inLengths, "D", generate_data(get_tensor_lengths())); - add(toReduceDims, "R", generate_data(get_toreduce_dims())); - add(reduceOp, "ReduceOp", generate_data({0, 1, 4, 5, 6, 7})); - add(compTypeVal, "CompType", generate_data({1})); - add(nanOpt, "N", generate_data({0, 1})); - add(indicesOpt, "I", generate_data({0, 1})); - - add(scales, "scales", generate_data({{1.0f, 0.0f}, {0.5f, 0.5f}})); - - auto&& handle = get_handle(); - handle.EnableProfiling(); - } - - void run() - { - using reduce::convert_type; - - if(std::is_same::value) - compTypeVal = static_cast(miopenDouble); - - if(std::is_same::value) - { - if(reduceOp == MIOPEN_REDUCE_TENSOR_MIN || reduceOp == MIOPEN_REDUCE_TENSOR_MAX || - reduceOp == MIOPEN_REDUCE_TENSOR_AMAX) - { - compTypeVal = static_cast(miopenHalf); // let compType be same as the data type - } - else - { - compTypeVal = static_cast(miopenFloat); - } - } - - miopen::ReduceTensorDescriptor reduceDesc( - static_cast(reduceOp), - static_cast(compTypeVal), - static_cast(nanOpt), - static_cast(indicesOpt), - indicesType); - - alpha = scales[0]; - beta = scales[1]; - - // The test is ignored if (alpha, beta) is not (1.0f, 0.0f) and reduceOp is not Add/MUL/AVG - if(reduceOp != MIOPEN_REDUCE_TENSOR_ADD && reduceOp != MIOPEN_REDUCE_TENSOR_MUL && - reduceOp != MIOPEN_REDUCE_TENSOR_AVG && alpha != 1.0f && beta != 0.0f) - return; - - // The test is ignored if indices are requested but the reduceOp is neither MIN nor MAX - if(indicesOpt != MIOPEN_REDUCE_TENSOR_NO_INDICES && reduceOp != MIOPEN_REDUCE_TENSOR_MIN && - reduceOp != MIOPEN_REDUCE_TENSOR_MAX && reduceOp != MIOPEN_REDUCE_TENSOR_AMAX) - return; - - auto outLengths = this->inLengths; - - assert(toReduceDims.size() <= outLengths.size()); - - // set the lengths of the dimensions to be reduced to 1 to represent the output Tensor - for(const int& toReduceDim : toReduceDims) - { - assert(toReduceDim < inLengths.size()); - outLengths[toReduceDim] = static_cast(1); - } - - uint64_t max_value; - - if(reduceOp == MIOPEN_REDUCE_TENSOR_MUL) - { - max_value = miopen_type{} == miopenHalf ? 41 - : miopen_type{} == miopenInt8 ? 127 - : 111; - } - else if(reduceOp == MIOPEN_REDUCE_TENSOR_NORM1 || reduceOp == MIOPEN_REDUCE_TENSOR_NORM2) - { - max_value = 3; - } - else - { - max_value = miopen_type{} == miopenHalf ? 13 - : miopen_type{} == miopenInt8 ? 127 - : 999; - } - - // default data gneration (used by MIN/MAX) - auto gen_value = [&](auto... is) { - return (tensor_elem_gen_integer{max_value}(is...) * - tensor_elem_gen_checkboard_sign{}(is...)); - }; - - // data generation used by ADD/AVG, data is distributed around 1.0 rather than 0.0, very low - // probability to get a reduced result of zero-value - auto gen_value_1 = [&](auto... is) { - auto rand_value = tensor_elem_gen_integer{max_value}(is...); - auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); - - return (sign_value * rand_value / max_value + 0.01); - }; - - // Special data generation for MUL, to avoid all-zero and large accumulative error in the - // reduced result - auto gen_value_2 = [&](auto... is) { - auto rand_value = tensor_elem_gen_integer{max_value}(is...); - auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); - - return sign_value > 0.0 ? (rand_value + max_value) / (rand_value + max_value + 1) - : (rand_value + max_value + 1) / (rand_value + max_value); - }; - - // Special data generation for NORM1 and NORM2 using a space of limitless number of values. - auto gen_value_3 = [&](auto... is) { - auto rand_upper = tensor_elem_gen_integer{max_value}(is...); - auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); - auto rand_ratio = prng::gen_A_to_B( - 0.1, 1.); // limit range due to numeric errors, see WORKAROUND_GPU_NUMERIC_ERROR - - return rand_upper * sign_value * rand_ratio; - }; - - // Special data generation for AMAX, no zero value used - auto gen_value_4 = [&](auto... is) { - auto rand_value = tensor_elem_gen_integer{max_value}(is...); - auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); - - return sign_value > 0.0 ? (rand_value + 0.5) : (-1.0 * rand_value - 0.5); - }; - - // default tolerance (refer to driver.hpp) - this->tolerance = 80; - - if(reduceOp == MIOPEN_REDUCE_TENSOR_ADD || reduceOp == MIOPEN_REDUCE_TENSOR_AVG) - this->tolerance = 80 * 10; - if(reduceOp == MIOPEN_REDUCE_TENSOR_MUL) - { - this->tolerance = 80 * 300; - } - else if(reduceOp == MIOPEN_REDUCE_TENSOR_NORM1 || reduceOp == MIOPEN_REDUCE_TENSOR_NORM2) - { - if(toReduceDims.size() == 4) - this->tolerance = 80 * 100; - else - this->tolerance = 80 * 10; - }; - - if(std::is_same::value) - this->tolerance *= this->tolerance * 10.0; - - tensor inputTensor; - - switch(reduceOp) - { - case MIOPEN_REDUCE_TENSOR_ADD: - case MIOPEN_REDUCE_TENSOR_AVG: - inputTensor = tensor{this->inLengths}.generate(gen_value_1); - break; - case MIOPEN_REDUCE_TENSOR_MUL: - inputTensor = tensor{this->inLengths}.generate(gen_value_2); - break; - case MIOPEN_REDUCE_TENSOR_NORM1: - case MIOPEN_REDUCE_TENSOR_NORM2: - inputTensor = tensor{this->inLengths}.generate(gen_value_3); - break; - case MIOPEN_REDUCE_TENSOR_AMAX: - inputTensor = tensor{this->inLengths}.generate(gen_value_4); - break; - default: inputTensor = tensor{this->inLengths}.generate(gen_value); - }; - - auto outputTensor = tensor{outLengths}; - - std::fill(outputTensor.begin(), outputTensor.end(), convert_type(0.0f)); - - auto indices_nelem = - reduceDesc.GetIndicesSize(inputTensor.desc, outputTensor.desc) / sizeof(int); - - auto ws_sizeInBytes = - reduceDesc.GetWorkspaceSize(get_handle(), inputTensor.desc, outputTensor.desc); - auto workspace_nelem = (indices_nelem == 0) ? ws_sizeInBytes / sizeof(T) - : (ws_sizeInBytes + sizeof(T) - 1) / sizeof(T); - - std::vector wsLengths = {static_cast(workspace_nelem), 1}; - auto workspaceTensor = tensor{wsLengths}; - - std::fill(workspaceTensor.begin(), workspaceTensor.end(), convert_type(0.0f)); - - if(indices_nelem > 0) - { - std::vector indicesLengths = {static_cast(indices_nelem), 1}; - auto indicesTensor = tensor{indicesLengths}; - - std::fill(indicesTensor.begin(), indicesTensor.end(), 1); - - verify(verify_reduce_with_indices( - reduceDesc, inputTensor, outputTensor, workspaceTensor, indicesTensor, 1.0f, 0.0f)); - - verify_equals(verify_reduce_with_indices( - reduceDesc, inputTensor, outputTensor, workspaceTensor, indicesTensor, 1.0f, 0.0f)); - } - else - { - verify(verify_reduce_no_indices( - reduceDesc, inputTensor, outputTensor, workspaceTensor, alpha, beta)); - }; - }; -}; +#include "reduce_test.hpp" int main(int argc, const char* argv[]) { diff --git a/test/reduce_test.hpp b/test/reduce_test.hpp new file mode 100644 index 0000000000..fc5ef76b3a --- /dev/null +++ b/test/reduce_test.hpp @@ -0,0 +1,948 @@ +/******************************************************************************* + * + * MIT License + * + * Copyright (c) 2020 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 "driver.hpp" +#include "test.hpp" +#include "verify.hpp" +#include "workspace.hpp" +#include "get_handle.hpp" +#include "tensor_holder.hpp" +#include "random.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpu_reduce_util.hpp" + +template +struct verify_reduce_with_indices +{ + miopen::ReduceTensorDescriptor reduce; + tensor input; + tensor output; + tensor workspace; + tensor indices; + float alpha; + float beta; + + miopenReduceTensorOp_t reduceOp; + miopenDataType_t compTypeVal; + miopenNanPropagation_t nanOpt; + miopenReduceTensorIndices_t indicesOpt; + miopenIndicesType_t indicesType; + + verify_reduce_with_indices(const miopen::ReduceTensorDescriptor& reduce_, + const tensor& input_, + const tensor& output_, + const tensor& workspace_, + const tensor& indices_, + float alpha_, + float beta_) + : reduce(reduce_), + input(input_), + output(output_), + workspace(workspace_), + indices(indices_), + alpha(alpha_), + beta(beta_), + reduceOp(reduce.reduceTensorOp_), + compTypeVal(reduce.reduceTensorCompType_), + nanOpt(reduce.reduceTensorNanOpt_), + indicesOpt(reduce.reduceTensorIndices_), + indicesType(reduce.reduceTensorIndicesType_) + { + } + + tensor cpu() const + { + using reduce::convert_type; + + std::tuple, tensor> results; + + if(compTypeVal == miopenFloat) + { + if(std::is_same::value) + results = cpuImpl(); + else + results = cpuImpl(); + } + else if(compTypeVal == miopenHalf) + { + if(std::is_same::value) + results = cpuImpl(); + else if(std::is_same::value) + results = cpuImpl(); + else + results = cpuImpl(); + } + else if(compTypeVal == miopenDouble) + results = cpuImpl(); + + if(toVerifyData) + { + const auto& dimLengths = output.desc.GetLengths(); + + auto result_dataFloat = tensor(dimLengths); + + auto& result_dataT = std::get<0>(results); + + for(size_t i = 0; i < result_dataT.data.size(); i++) + result_dataFloat.data[i] = convert_type(result_dataT.data[i]); + + return (result_dataFloat); + } + else + { + const auto& dimLengths = indices.desc.GetLengths(); + + auto result_indicesFloat = tensor(dimLengths); + + auto& result_indices = std::get<1>(results); + + for(size_t i = 0; i < result_indices.data.size(); i++) + result_indicesFloat.data[i] = static_cast(result_indices.data[i]); + + return (result_indicesFloat); + }; + }; + + tensor gpu() const + { + using reduce::convert_type; + + std::tuple, tensor> results; + + results = gpuImpl(); + + if(toVerifyData) + { + const auto& dimLengths = output.desc.GetLengths(); + + auto result_dataFloat = tensor(dimLengths); + + tensor& result_dataT = std::get<0>(results); + + for(size_t i = 0; i < result_dataT.data.size(); i++) + result_dataFloat.data[i] = convert_type(result_dataT.data[i]); + + return (result_dataFloat); + } + else + { + const auto& dimLengths = indices.desc.GetLengths(); + + auto result_indicesFloat = tensor(dimLengths); + + tensor& result_indices = std::get<1>(results); + + for(size_t i = 0; i < result_indices.data.size(); i++) + result_indicesFloat.data[i] = static_cast(result_indices.data[i]); + + return (result_indicesFloat); + }; + }; + + template + std::tuple, tensor> cpuImpl() const + { + using reduce::binop_with_nan_check; + using reduce::binop_with_nan_check2; + using reduce::convert_type; + using reduce::float_equal_one; + using reduce::float_equal_zero; + using reduce::PosUnaryOpFn; + using reduce::PreUnaryOpFn; + using reduce::ReduceOpFn2; + using reduce::ReduceOpZeroVal; + + auto inLengths = input.desc.GetLengths(); + auto outLengths = output.desc.GetLengths(); + auto inStrides = input.desc.GetStrides(); + auto outStrides = output.desc.GetStrides(); + + // replicate + auto res = output; + auto res_indices = indices; + + std::vector invariantLengths; + std::vector toReduceLengths; + + std::vector invariantDims; + std::vector toReduceDims; + + for(int i = 0; i < inLengths.size(); i++) + { + if(inLengths[i] == outLengths[i]) + invariantDims.push_back(i); + else + toReduceDims.push_back(i); + } + + invariantLengths.resize(invariantDims.size()); + for(int i = 0; i < invariantDims.size(); i++) + invariantLengths[i] = inLengths[invariantDims[i]]; + + toReduceLengths.resize(toReduceDims.size()); + for(int i = 0; i < toReduceDims.size(); i++) + toReduceLengths[i] = inLengths[toReduceDims[i]]; + + bool reduceAllDims = invariantDims.empty(); + + auto opReduce = ReduceOpFn2(reduceOp); + + std::size_t divider = std::accumulate( + toReduceLengths.begin(), toReduceLengths.end(), std::size_t{1}, std::multiplies<>{}); + + auto PreUnaryOp = PreUnaryOpFn(reduceOp, divider); + + if(reduceAllDims) + { + std::vector> indexes_1; + + get_all_indexes(inLengths, 0, indexes_1); + + compType accuVal = ReduceOpZeroVal(reduceOp); + int accuIndex = 0; + + // go through indexes of the invariant dimensions + for(const auto& src_index : indexes_1) + { + auto src_offset = get_offset_from_index(inStrides, src_index); + + auto currVal = convert_type(input.data[src_offset]); + + // unary operation before reducing, only needed by AMAX. For MIN/MAX, nothing is + // actually done + PreUnaryOp(currVal); + + int currIndex = get_flatten_offset(inLengths, src_index); + binop_with_nan_check2(nanOpt, opReduce, accuVal, currVal, accuIndex, currIndex); + } + + // scale the accumulated value + if(!float_equal_one(alpha)) + accuVal *= convert_type(alpha); + + // scale the prior dst value and add it to the accumulated value + if(!float_equal_zero(beta)) + { + accuVal += convert_type(output.data[0]) * convert_type(beta); + } + + // store the reduced value to dst location + res.data[0] = convert_type(accuVal); + res_indices.data[0] = accuIndex; + } + else + { + std::vector> indexes_1, indexes_2; + + get_all_indexes(invariantLengths, 0, indexes_1); + get_all_indexes(toReduceLengths, 0, indexes_2); + + // go through indexes of the invariant dimensions + for(const auto& index_1 : indexes_1) + { + std::vector src_index; + std::vector dst_index; + + src_index.resize(inLengths.size()); + dst_index.resize(inLengths.size()); + + std::fill(dst_index.begin(), dst_index.end(), 0); + + for(int k = 0; k < invariantDims.size(); k++) + dst_index[invariantDims[k]] = index_1[k]; + + auto dst_offset = get_offset_from_index(outStrides, dst_index); + + // generate the part of the index belonging to the invariant dims + for(int k = 0; k < invariantDims.size(); k++) + src_index[invariantDims[k]] = index_1[k]; + + compType accuVal = ReduceOpZeroVal(reduceOp); + int accuIndex = 0; + + // go through indexes of the toReduce dimensions + for(const auto& index_2 : indexes_2) + { + // generate the part of the index belonging to the toReduce dims + for(int k = 0; k < toReduceDims.size(); k++) + src_index[toReduceDims[k]] = index_2[k]; + + auto src_offset = get_offset_from_index(inStrides, src_index); + + auto currVal = convert_type(input.data[src_offset]); + + // unary operation before reducing, only needed by AMAX. For MIN/MAX, nothing is + // actually done + PreUnaryOp(currVal); + + auto currIndex = get_flatten_offset(toReduceLengths, index_2); + binop_with_nan_check2(nanOpt, opReduce, accuVal, currVal, accuIndex, currIndex); + }; + + // scale the accumulated value + if(!float_equal_one(alpha)) + accuVal *= convert_type(alpha); + + // scale the prior dst value and add it to the accumulated value + if(!float_equal_zero(beta)) + { + accuVal += convert_type(output.data[dst_offset]) * + convert_type(beta); + } + + // store the reduced value to dst location + res.data[dst_offset] = convert_type(accuVal); + res_indices.data[dst_offset] = accuIndex; // store the index + }; + }; + + return (std::make_tuple(res, res_indices)); + } + + std::tuple, tensor> gpuImpl() const + { + auto&& handle = get_handle(); + auto input_dev = handle.Write(input.data); + auto output_dev = handle.Write(output.data); + + // replicate + auto res = output; + auto res_indices = indices; + + Workspace idxspace{}; + idxspace.Write(indices.data); + + Workspace wspace{}; + wspace.Write(workspace.data); + + const double alpha64 = alpha; + const double beta64 = beta; + + const void* const alphaPtr = (std::is_same::value) + ? static_cast(&alpha64) + : static_cast(&alpha); + const void* const betaPtr = (std::is_same::value) + ? static_cast(&beta64) + : static_cast(&beta); + + if(wspace.size() > 0) + { + reduce.ReduceTensor(get_handle(), + idxspace.ptr(), + idxspace.size(), + wspace.ptr(), + wspace.size(), + alphaPtr, + input.desc, + input_dev.get(), + betaPtr, + output.desc, + output_dev.get()); + } + else + { + reduce.ReduceTensor(get_handle(), + idxspace.ptr(), + idxspace.size(), + nullptr, + 0, + alphaPtr, + input.desc, + input_dev.get(), + betaPtr, + output.desc, + output_dev.get()); + }; + + res.data = handle.Read(output_dev, res.data.size()); + res_indices.data = idxspace.Read(); + + return (std::make_tuple(res, res_indices)); + } + + void fail(int) const + { + std::cout << "verify_reduce_with_indices failed" << std::endl; + std::cout << "Input Tensor" + << " " << input.desc.ToString() << std::endl; + } +}; + +template +struct verify_reduce_no_indices +{ + miopen::ReduceTensorDescriptor reduce; + tensor input; + tensor output; + tensor workspace; + float alpha; + float beta; + + miopenReduceTensorOp_t reduceOp; + miopenDataType_t compTypeVal; + miopenNanPropagation_t nanOpt; + + verify_reduce_no_indices( // NOLINT (hicpp-member-init) + const miopen::ReduceTensorDescriptor& reduce_, + const tensor& input_, + const tensor& output_, + const tensor& workspace_, + float alpha_, + float beta_) + : reduce(reduce_), + input(input_), + output(output_), + workspace(workspace_), + alpha(alpha_), + beta(beta_), + reduceOp(reduce_.reduceTensorOp_), + compTypeVal(reduce_.reduceTensorCompType_), + nanOpt(reduce_.reduceTensorNanOpt_) + { + } + + tensor cpu() + { + using reduce::convert_type; + + tensor result; + + if(compTypeVal == miopenFloat) + { + if(std::is_same::value) + result = cpuImpl(); + else + result = cpuImpl(); + } + else if(compTypeVal == miopenHalf) + { + if(std::is_same::value) + result = cpuImpl(); + else if(std::is_same::value) + result = cpuImpl(); + else + result = cpuImpl(); + } + else if(compTypeVal == miopenDouble) + result = cpuImpl(); + + const auto& dimLengths = output.desc.GetLengths(); + auto result_dataFloat = tensor(dimLengths); + + for(size_t i = 0; i < result.data.size(); i++) + result_dataFloat.data[i] = convert_type(result.data[i]); + + return (result_dataFloat); + }; + + template + tensor cpuImpl() const + { + using reduce::binop_with_nan_check; + using reduce::binop_with_nan_check2; + using reduce::convert_type; + using reduce::float_equal_one; + using reduce::float_equal_zero; + using reduce::PosUnaryOpFn; + using reduce::PreUnaryOpFn; + using reduce::ReduceOpFn; + using reduce::ReduceOpZeroVal; + + auto inLengths = input.desc.GetLengths(); + auto outLengths = output.desc.GetLengths(); + auto inStrides = input.desc.GetStrides(); + auto outStrides = output.desc.GetStrides(); + + // replicate + auto res = output; + + std::vector invariantLengths; + std::vector toReduceLengths; + + std::vector invariantDims; + std::vector toReduceDims; + + for(int i = 0; i < inLengths.size(); i++) + { + if(inLengths[i] == outLengths[i]) + invariantDims.push_back(i); + else + toReduceDims.push_back(i); + } + + invariantLengths.resize(invariantDims.size()); + for(int i = 0; i < invariantDims.size(); i++) + invariantLengths[i] = inLengths[invariantDims[i]]; + + toReduceLengths.resize(toReduceDims.size()); + for(int i = 0; i < toReduceDims.size(); i++) + toReduceLengths[i] = inLengths[toReduceDims[i]]; + + bool reduceAllDims = invariantDims.empty(); + + auto opReduce = ReduceOpFn(reduceOp); + + std::size_t divider = std::accumulate( + toReduceLengths.begin(), toReduceLengths.end(), std::size_t{1}, std::multiplies<>{}); + + auto PreUnaryOp = PreUnaryOpFn(reduceOp, divider); + auto PosUnaryOp = PosUnaryOpFn(reduceOp, divider); + + if(reduceAllDims) + { + std::vector> indexes_1; + + get_all_indexes(inLengths, 0, indexes_1); + + compType accuVal = ReduceOpZeroVal(reduceOp); + + // go through indexes of the invariant dimensions + for(const auto& src_index : indexes_1) + { + auto src_offset = get_offset_from_index(inStrides, src_index); + + auto currVal = convert_type(input.data[src_offset]); + + PreUnaryOp(currVal); + + binop_with_nan_check(nanOpt, opReduce, accuVal, currVal); + }; + + PosUnaryOp(accuVal); + + // scale the accumulated value + if(!float_equal_one(alpha)) + accuVal *= convert_type(alpha); + + // scale the prior dst value and add it to the accumulated value + if(!float_equal_zero(beta)) + accuVal += convert_type(output.data[0]) * convert_type(beta); + + // store the reduced value to dst location + res.data[0] = convert_type(accuVal); + } + else + { + std::vector> indexes_1, indexes_2; + + get_all_indexes(invariantLengths, 0, indexes_1); + get_all_indexes(toReduceLengths, 0, indexes_2); + + // go through indexes of the invariant dimensions + for(const auto& index_1 : indexes_1) + { + std::vector src_index; + std::vector dst_index; + + src_index.resize(inLengths.size()); + dst_index.resize(inLengths.size()); + + std::fill(dst_index.begin(), dst_index.end(), 0); + + for(int k = 0; k < invariantDims.size(); k++) + dst_index[invariantDims[k]] = index_1[k]; + + auto dst_offset = get_offset_from_index(outStrides, dst_index); + + // generate the part of the index belonging to the invariant dims + for(int k = 0; k < invariantDims.size(); k++) + src_index[invariantDims[k]] = index_1[k]; + + compType accuVal = ReduceOpZeroVal(reduceOp); + + // go through indexes of the toReduce dimensions + for(const auto& index_2 : indexes_2) + { + // generate the part of the index belonging to the toReduce dims + for(int k = 0; k < toReduceDims.size(); k++) + src_index[toReduceDims[k]] = index_2[k]; + + auto src_offset = get_offset_from_index(inStrides, src_index); + + auto currVal = convert_type(input.data[src_offset]); + + PreUnaryOp(currVal); + + binop_with_nan_check(nanOpt, opReduce, accuVal, currVal); + }; + + PosUnaryOp(accuVal); + + // scale the accumulated value + if(!float_equal_one(alpha)) + accuVal *= convert_type(alpha); + + // scale the prior dst value and add it to the accumulated value + if(!float_equal_zero(beta)) + { + accuVal += convert_type(output.data[dst_offset]) * + convert_type(beta); + } + + // store the reduced value to dst location + res.data[dst_offset] = convert_type(accuVal); + }; + }; + + return (res); + } + + tensor gpu() const + { + using reduce::convert_type; + + auto result = gpuImpl(); + + const auto& dimLengths = output.desc.GetLengths(); + auto result_dataFloat = tensor(dimLengths); + + for(size_t i = 0; i < result.data.size(); i++) + result_dataFloat.data[i] = convert_type(result.data[i]); + + return (result_dataFloat); + }; + + tensor gpuImpl() const + { + auto&& handle = get_handle(); + auto input_dev = handle.Write(input.data); + auto output_dev = handle.Write(output.data); + + // replicate + auto res = output; + + Workspace wspace{}; + wspace.Write(workspace.data); + + const double alpha64 = alpha; + const double beta64 = beta; + + const void* const alphaPtr = (std::is_same::value) + ? static_cast(&alpha64) + : static_cast(&alpha); + const void* const betaPtr = (std::is_same::value) + ? static_cast(&beta64) + : static_cast(&beta); + + if(wspace.size() > 0) + { + reduce.ReduceTensor(get_handle(), + nullptr, + 0, + wspace.ptr(), + wspace.size(), + alphaPtr, + input.desc, + input_dev.get(), + betaPtr, + output.desc, + output_dev.get()); + } + else + { + reduce.ReduceTensor(get_handle(), + nullptr, + 0, + nullptr, + 0, + alphaPtr, + input.desc, + input_dev.get(), + betaPtr, + output.desc, + output_dev.get()); + }; + + res.data = handle.Read(output_dev, res.data.size()); + + return (res); + } + + void fail(int) const + { + std::cout << "verify_reduce_no_indices failed" << std::endl; + std::cout << "Input Tensor" + << " " << input.desc.ToString() << std::endl; + } +}; + +template +struct reduce_driver : test_driver +{ + int reduceOp = 0; // miopenReduceTensorOp_t reduceOp; + int compTypeVal = 1; // miopenDataType_t compTypeVal; + int nanOpt = 0; // miopenNanPropagation_t nanOpt; + int indicesOpt = 0; // miopenReduceTensorIndices_t indicesOpt; + miopenIndicesType_t indicesType = MIOPEN_32BIT_INDICES; + + std::vector inLengths; // the lengths of the input tensor's dimensions + std::vector + toReduceDims; // the indexes of the dimensions to be reduced in the input tensor + + std::vector scales; + float alpha = 1.0f; + float beta = 0.0f; + + std::vector> get_tensor_lengths() + { + if(std::is_same::value) + { + return { + {4, 3, 60, 50}, + }; + } + else + { + return { + {64, 3, 280, 81}, + }; + } + } + + std::vector> get_toreduce_dims() + { + std::vector> tensor_dims = { + {0}, {1}, {2}, {3}, {0, 1}, {0, 3}, {0, 2}, {2, 3}, {0, 1, 3}, {1, 2, 3}, {0, 1, 2, 3}}; + + return tensor_dims; + } + + reduce_driver() + { + add(inLengths, "D", generate_data(get_tensor_lengths())); + add(toReduceDims, "R", generate_data(get_toreduce_dims())); + add(reduceOp, "ReduceOp", generate_data({0, 1, 4, 5, 6, 7})); + add(compTypeVal, "CompType", generate_data({1})); + add(nanOpt, "N", generate_data({0, 1})); + add(indicesOpt, "I", generate_data({0, 1})); + + add(scales, "scales", generate_data({{1.0f, 0.0f}, {0.5f, 0.5f}})); + + auto&& handle = get_handle(); + handle.EnableProfiling(); + } + + void run() + { + using reduce::convert_type; + + if(std::is_same::value) + compTypeVal = static_cast(miopenDouble); + + if(std::is_same::value) + { + if(reduceOp == MIOPEN_REDUCE_TENSOR_MIN || reduceOp == MIOPEN_REDUCE_TENSOR_MAX || + reduceOp == MIOPEN_REDUCE_TENSOR_AMAX) + { + compTypeVal = static_cast(miopenHalf); // let compType be same as the data type + } + else + { + compTypeVal = static_cast(miopenFloat); + } + } + + miopen::ReduceTensorDescriptor reduceDesc( + static_cast(reduceOp), + static_cast(compTypeVal), + static_cast(nanOpt), + static_cast(indicesOpt), + indicesType); + + alpha = scales[0]; + beta = scales[1]; + + // The test is ignored if (alpha, beta) is not (1.0f, 0.0f) and reduceOp is not Add/MUL/AVG + if(reduceOp != MIOPEN_REDUCE_TENSOR_ADD && reduceOp != MIOPEN_REDUCE_TENSOR_MUL && + reduceOp != MIOPEN_REDUCE_TENSOR_AVG && alpha != 1.0f && beta != 0.0f) + return; + + // The test is ignored if indices are requested but the reduceOp is neither MIN nor MAX + if(indicesOpt != MIOPEN_REDUCE_TENSOR_NO_INDICES && reduceOp != MIOPEN_REDUCE_TENSOR_MIN && + reduceOp != MIOPEN_REDUCE_TENSOR_MAX && reduceOp != MIOPEN_REDUCE_TENSOR_AMAX) + return; + + auto outLengths = this->inLengths; + + assert(toReduceDims.size() <= outLengths.size()); + + // set the lengths of the dimensions to be reduced to 1 to represent the output Tensor + for(const int& toReduceDim : toReduceDims) + { + assert(toReduceDim < inLengths.size()); + outLengths[toReduceDim] = static_cast(1); + } + + uint64_t max_value; + + if(reduceOp == MIOPEN_REDUCE_TENSOR_MUL) + { + max_value = miopen_type{} == miopenHalf ? 41 + : miopen_type{} == miopenInt8 ? 127 + : 111; + } + else if(reduceOp == MIOPEN_REDUCE_TENSOR_NORM1 || reduceOp == MIOPEN_REDUCE_TENSOR_NORM2) + { + max_value = 3; + } + else + { + max_value = miopen_type{} == miopenHalf ? 13 + : miopen_type{} == miopenInt8 ? 127 + : 999; + } + + // default data gneration (used by MIN/MAX) + auto gen_value = [&](auto... is) { + return (tensor_elem_gen_integer{max_value}(is...) * + tensor_elem_gen_checkboard_sign{}(is...)); + }; + + // data generation used by ADD/AVG, data is distributed around 1.0 rather than 0.0, very low + // probability to get a reduced result of zero-value + auto gen_value_1 = [&](auto... is) { + auto rand_value = tensor_elem_gen_integer{max_value}(is...); + auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); + + return (sign_value * rand_value / max_value + 0.01); + }; + + // Special data generation for MUL, to avoid all-zero and large accumulative error in the + // reduced result + auto gen_value_2 = [&](auto... is) { + auto rand_value = tensor_elem_gen_integer{max_value}(is...); + auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); + + return sign_value > 0.0 ? (rand_value + max_value) / (rand_value + max_value + 1) + : (rand_value + max_value + 1) / (rand_value + max_value); + }; + + // Special data generation for NORM1 and NORM2 using a space of limitless number of values. + auto gen_value_3 = [&](auto... is) { + auto rand_upper = tensor_elem_gen_integer{max_value}(is...); + auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); + auto rand_ratio = prng::gen_A_to_B( + 0.1, 1.); // limit range due to numeric errors, see WORKAROUND_GPU_NUMERIC_ERROR + + return rand_upper * sign_value * rand_ratio; + }; + + // Special data generation for AMAX, no zero value used + auto gen_value_4 = [&](auto... is) { + auto rand_value = tensor_elem_gen_integer{max_value}(is...); + auto sign_value = tensor_elem_gen_checkboard_sign{}(is...); + + return sign_value > 0.0 ? (rand_value + 0.5) : (-1.0 * rand_value - 0.5); + }; + + // default tolerance (refer to driver.hpp) + this->tolerance = 80; + + if(reduceOp == MIOPEN_REDUCE_TENSOR_ADD || reduceOp == MIOPEN_REDUCE_TENSOR_AVG) + this->tolerance = 80 * 10; + if(reduceOp == MIOPEN_REDUCE_TENSOR_MUL) + { + this->tolerance = 80 * 300; + } + else if(reduceOp == MIOPEN_REDUCE_TENSOR_NORM1 || reduceOp == MIOPEN_REDUCE_TENSOR_NORM2) + { + if(toReduceDims.size() == 4) + this->tolerance = 80 * 100; + else + this->tolerance = 80 * 10; + }; + + if(std::is_same::value) + this->tolerance *= this->tolerance * 10.0; + + tensor inputTensor; + + switch(reduceOp) + { + case MIOPEN_REDUCE_TENSOR_ADD: + case MIOPEN_REDUCE_TENSOR_AVG: + inputTensor = tensor{this->inLengths}.generate(gen_value_1); + break; + case MIOPEN_REDUCE_TENSOR_MUL: + inputTensor = tensor{this->inLengths}.generate(gen_value_2); + break; + case MIOPEN_REDUCE_TENSOR_NORM1: + case MIOPEN_REDUCE_TENSOR_NORM2: + inputTensor = tensor{this->inLengths}.generate(gen_value_3); + break; + case MIOPEN_REDUCE_TENSOR_AMAX: + inputTensor = tensor{this->inLengths}.generate(gen_value_4); + break; + default: inputTensor = tensor{this->inLengths}.generate(gen_value); + }; + + auto outputTensor = tensor{outLengths}; + + std::fill(outputTensor.begin(), outputTensor.end(), convert_type(0.0f)); + + auto indices_nelem = + reduceDesc.GetIndicesSize(inputTensor.desc, outputTensor.desc) / sizeof(int); + + auto ws_sizeInBytes = + reduceDesc.GetWorkspaceSize(get_handle(), inputTensor.desc, outputTensor.desc); + auto workspace_nelem = (indices_nelem == 0) ? ws_sizeInBytes / sizeof(T) + : (ws_sizeInBytes + sizeof(T) - 1) / sizeof(T); + + std::vector wsLengths = {static_cast(workspace_nelem), 1}; + auto workspaceTensor = tensor{wsLengths}; + + std::fill(workspaceTensor.begin(), workspaceTensor.end(), convert_type(0.0f)); + + if(indices_nelem > 0) + { + std::vector indicesLengths = {static_cast(indices_nelem), 1}; + auto indicesTensor = tensor{indicesLengths}; + + std::fill(indicesTensor.begin(), indicesTensor.end(), 1); + + verify(verify_reduce_with_indices( + reduceDesc, inputTensor, outputTensor, workspaceTensor, indicesTensor, 1.0f, 0.0f)); + + verify_equals(verify_reduce_with_indices( + reduceDesc, inputTensor, outputTensor, workspaceTensor, indicesTensor, 1.0f, 0.0f)); + } + else + { + verify(verify_reduce_no_indices( + reduceDesc, inputTensor, outputTensor, workspaceTensor, alpha, beta)); + }; + }; +}; diff --git a/test/rnn_seq_api.hpp b/test/rnn_seq_api.hpp index 38f834d772..d6677b7db9 100644 --- a/test/rnn_seq_api.hpp +++ b/test/rnn_seq_api.hpp @@ -1640,7 +1640,7 @@ struct rnn_seq_api_test_driver : test_driver fill_buffers(input, dy, hx, cx, dhy, dcy, weights); // avoid BWD unexpected fails - // https://github.com/ROCmSoftwarePlatform/MIOpen/pull/2493#discussion_r1406959588 + // https://github.com/ROCm/MIOpen/pull/2493#discussion_r1406959588 if(inVecLen == 1 && hiddenSize == 13 && seqLength == 1 && batchSize == 1) { tolerance = 110; diff --git a/test/rnn_util.hpp b/test/rnn_util.hpp index 680c52582f..eeeebcadf9 100644 --- a/test/rnn_util.hpp +++ b/test/rnn_util.hpp @@ -115,7 +115,7 @@ inline void createTensorDescArray(std::vector& td, inline std::tuple GetTempPackedBuffersSize(std::vector batchs, int in_vec, int out_vec) { - size_t total_batch = std::accumulate(batchs.begin(), batchs.end(), 0); + size_t total_batch = std::accumulate(batchs.begin(), batchs.end(), 0ULL); size_t in_buff_size = total_batch * in_vec; size_t out_buff_size = total_batch * out_vec; @@ -131,9 +131,12 @@ inline size_t getSuperTensorSize(const std::vector& bs, bool isInput, bool isPadded) { - return static_cast(isPadded ? seqLength * maxPaddingVal - : std::accumulate(bs.begin(), bs.end(), 0)) * - static_cast(isInput ? inputSize : hiddenSize * (isBidirect ? 2 : 1)); + return (isPadded // + ? static_cast(seqLength) * maxPaddingVal + : std::accumulate(bs.begin(), bs.end(), 0ULL)) // + * (isInput // + ? static_cast(inputSize) + : static_cast(hiddenSize) * (isBidirect ? 2 : 1)); } template diff --git a/test/serialize.hpp b/test/serialize.hpp index 14ff5be267..10841c0162 100644 --- a/test/serialize.hpp +++ b/test/serialize.hpp @@ -29,11 +29,7 @@ #include #include -#if !defined(_WIN32) #include -#else -#include -#endif #include #include #include diff --git a/test/solver.cpp b/test/solver.cpp index 020fd8ed85..93cac5c819 100644 --- a/test/solver.cpp +++ b/test/solver.cpp @@ -146,7 +146,7 @@ static solver::ConvSolution FindSolution(const ExecutionContext& ctx, const conv::ProblemDescription& problem, const std::string& db_path) { - PlainTextDb db(db_path); + PlainTextDb db(DbKinds::PerfDb, db_path); const auto solvers = solver::SolverContainer{}; diff --git a/test/sqlite.cpp b/test/sqlite.cpp index dfb1a5b7e6..29b6047e6f 100644 --- a/test/sqlite.cpp +++ b/test/sqlite.cpp @@ -45,7 +45,7 @@ bool test_lfs_db(bool is_system) tmp_db_file << lfs_db; tmp_db_file.close(); // construct a db out of it - miopen::SQLiteBase lfs_sqdb{tmp_db, is_system}; + miopen::SQLiteBase lfs_sqdb{miopen::DbKinds::PerfDb, tmp_db, is_system}; return lfs_sqdb.dbInvalid; } diff --git a/test/sqlite_perfdb.cpp b/test/sqlite_perfdb.cpp index 5d9a25e4a8..7c361cbe78 100644 --- a/test/sqlite_perfdb.cpp +++ b/test/sqlite_perfdb.cpp @@ -244,7 +244,10 @@ std::ostream& operator<<(std::ostream& s, const SolverData& td) class DbTest { public: - DbTest() : temp_file("miopen.tests.perfdb"), db_inst{std::string(temp_file), false} {} + DbTest() + : temp_file("miopen.tests.perfdb"), db_inst{DbKinds::PerfDb, std::string(temp_file), false} + { + } virtual ~DbTest() {} @@ -335,7 +338,7 @@ class DbTest const TKey& key, const std::array, count> values) { - SQLitePerfDb tmp_inst(std::string(db_path), false); + SQLitePerfDb tmp_inst(DbKinds::PerfDb, std::string(db_path), false); for(const auto& id_values : values) { tmp_inst.UpdateUnsafe(key, id_values.first, id_values.second); @@ -411,7 +414,7 @@ class DbOperationsTest : public DbTest const SolverData to_be_rewritten(7, 8); { - SQLitePerfDb db(std::string(temp_file), false); + SQLitePerfDb db(DbKinds::PerfDb, std::string(temp_file), false); EXPECT(db.Update(p, id0(), to_be_rewritten)); EXPECT(db.Update(p, id1(), to_be_rewritten)); @@ -425,7 +428,7 @@ class DbOperationsTest : public DbTest } { - SQLitePerfDb db(std::string(temp_file), false); + SQLitePerfDb db(DbKinds::PerfDb, std::string(temp_file), false); // Rewriting existing value to store it to file. EXPECT(db.Update(p, id0(), value0())); @@ -434,7 +437,7 @@ class DbOperationsTest : public DbTest { SolverData read0, read1, read_missing; const auto read_missing_cmp(read_missing); - SQLitePerfDb db(std::string(temp_file), false); + SQLitePerfDb db(DbKinds::PerfDb, std::string(temp_file), false); // Loading by id not present in record should execute well but return false as nothing // was read. @@ -463,7 +466,7 @@ class DbOperationsTest : public DbTest { SolverData read0, read1; const auto read_missing_cmp(read0); - SQLitePerfDb db(std::string(temp_file), false); + SQLitePerfDb db(DbKinds::PerfDb, std::string(temp_file), false); EXPECT(!db.Load(p, id0(), read0)); EXPECT(db.Load(p, id1(), read1)); @@ -484,12 +487,12 @@ class DbParallelTest : public DbTest ProblemData p; - SQLitePerfDb db(std::string(temp_file), false); + SQLitePerfDb db(DbKinds::PerfDb, std::string(temp_file), false); EXPECT(db.Update(p, id0(), value0())); { - SQLitePerfDb db0(std::string(temp_file), false); - SQLitePerfDb db1(std::string(temp_file), false); + SQLitePerfDb db0(DbKinds::PerfDb, std::string(temp_file), false); + SQLitePerfDb db1(DbKinds::PerfDb, std::string(temp_file), false); auto r0 = db0.FindRecord(p); auto r1 = db1.FindRecord(p); @@ -509,7 +512,7 @@ class DbParallelTest : public DbTest EXPECT(db.Update(p, id1(), value1())); EXPECT(db.Update(p, id2(), value2())); - ValidateSingleEntry(p, data, SQLitePerfDb(temp_file, false)); + ValidateSingleEntry(p, data, SQLitePerfDb(DbKinds::PerfDb, temp_file, false)); } }; @@ -757,7 +760,7 @@ class DbMultiThreadedTest : public DbTest std::cout << "Launching test threads..." << std::endl; threads.reserve(DBMultiThreadedTestWork::threads_count); const std::string p = temp_file; - const auto c = [&p]() { return SQLitePerfDb(p, false); }; + const auto c = [&p]() { return SQLitePerfDb(DbKinds::PerfDb, p, false); }; { std::unique_lock lock(mutex); @@ -792,7 +795,7 @@ class DbMultiThreadedReadTest : public DbTest std::cout << "Initializing test data..." << std::endl; const std::string p = temp_file; - const auto c = [&p]() { return SQLitePerfDb(p, false); }; + const auto c = [&p]() { return SQLitePerfDb(DbKinds::PerfDb, p, false); }; DBMultiThreadedTestWork::FillForReading(c); std::cout << "Launching test threads..." << std::endl; @@ -871,7 +874,7 @@ class DbMultiProcessTest : public DbTest fs::remove(lock_file_path); const std::string p = temp_file; - const auto c = [&p]() { return SQLitePerfDb(p, false); }; + const auto c = [&p]() { return SQLitePerfDb(DbKinds::PerfDb, p, false); }; std::cout << "Validating results..." << std::endl; DBMultiThreadedTestWork::ValidateCommonPart(c); @@ -885,7 +888,7 @@ class DbMultiProcessTest : public DbTest std::lock_guard lock(file_lock); } - const auto c = [&db_path]() { return SQLitePerfDb(db_path, false); }; + const auto c = [&db_path]() { return SQLitePerfDb(DbKinds::PerfDb, db_path, false); }; if(write) DBMultiThreadedTestWork::WorkItem(id, c, "mp"); @@ -910,7 +913,7 @@ class DbMultiProcessReadTest : public DbTest std::cout << "Initializing test data..." << std::endl; std::string p = temp_file; - const auto c = [&p]() { return SQLitePerfDb(p, false); }; + const auto c = [&p]() { return SQLitePerfDb(DbKinds::PerfDb, p, false); }; DBMultiThreadedTestWork::FillForReading(c); std::cout << "Launching test processes..." << std::endl; @@ -935,7 +938,7 @@ class DbMultiProcessReadTest : public DbTest if(full_set()) args += " --all"; - std::cout << exe_path().string() + " " + args << std::endl; + std::cout << exe_path() << " " << args << std::endl; children.emplace_back(exe_path(), args); } // clang-format on @@ -956,7 +959,7 @@ class DbMultiProcessReadTest : public DbTest auto& file_lock = LockFile::Get(LockFilePath(db_path).c_str()); std::lock_guard lock(file_lock); } - const auto c = [&db_path]() { return SQLitePerfDb(db_path, false); }; + const auto c = [&db_path]() { return SQLitePerfDb(DbKinds::PerfDb, db_path, false); }; DBMultiThreadedTestWork::WorkItem(id, c, "mp"); } @@ -1019,13 +1022,15 @@ class DbMultiFileReadTest : public DbMultiFileTest {id0(), value2()}, }}; - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db( + DbKinds::PerfDb, temp_file, user_db_path); if(merge_records) ValidateSingleEntry(key(), merged_data, std::move(db)); else ValidateSingleEntry(key(), single_item_data(), std::move(db)); - MultiFileDb db1(temp_file, user_db_path); + MultiFileDb db1( + DbKinds::PerfDb, temp_file, user_db_path); ProblemData p; auto record1 = db1.FindRecord(p); EXPECT(!record1); @@ -1034,19 +1039,19 @@ class DbMultiFileReadTest : public DbMultiFileTest void ReadUser() const { RawWrite(user_db_path, key(), single_item_data()); - ValidateSingleEntry( - key(), - single_item_data(), - MultiFileDb(temp_file, user_db_path)); + ValidateSingleEntry(key(), + single_item_data(), + MultiFileDb( + DbKinds::PerfDb, temp_file, user_db_path)); } void ReadInstalled() const { RawWrite(temp_file, key(), single_item_data()); - ValidateSingleEntry( - key(), - single_item_data(), - MultiFileDb(temp_file, user_db_path)); + ValidateSingleEntry(key(), + single_item_data(), + MultiFileDb( + DbKinds::PerfDb, temp_file, user_db_path)); } void ReadConflict() const @@ -1065,16 +1070,18 @@ class DbMultiFileWriteTest : public DbMultiFileTest ResetDb(); { - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db( + DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.StoreRecord(key(), id0(), value0())); EXPECT(db.Update(key(), id1(), value1())); } - EXPECT(!SQLitePerfDb(temp_file, false).FindRecord(key())); - EXPECT(SQLitePerfDb(user_db_path, false).FindRecord(key())); + EXPECT(!SQLitePerfDb(DbKinds::PerfDb, temp_file, false).FindRecord(key())); + EXPECT(SQLitePerfDb(DbKinds::PerfDb, user_db_path, false).FindRecord(key())); ValidateSingleEntry(key(), common_data(), - MultiFileDb(temp_file, user_db_path)); + MultiFileDb( + DbKinds::PerfDb, temp_file, user_db_path)); } }; @@ -1096,7 +1103,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest std::cout << "Running multifile operations test..." << std::endl; { - SQLitePerfDb db(temp_file, false); + SQLitePerfDb db(DbKinds::PerfDb, temp_file, false); EXPECT(db.StoreRecord(key(), id0(), value0())); EXPECT(db.Update(key(), id1(), value2())); } @@ -1107,12 +1114,13 @@ class DbMultiFileOperationsTest : public DbMultiFileTest std::cout << "Update test..." << std::endl; { - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db( + DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.Update(key(), id1(), value1())); } { - SQLitePerfDb db(user_db_path, false); + SQLitePerfDb db(DbKinds::PerfDb, user_db_path, false); SolverData read(SolverData::NoInit{}); EXPECT(!db.Load(key(), id0(), read)); EXPECT(db.Load(key(), id1(), read)); @@ -1120,7 +1128,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest } { - SQLitePerfDb db(temp_file, false); + SQLitePerfDb db(DbKinds::PerfDb, temp_file, false); ValidateData(db, value2()); } } @@ -1129,7 +1137,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest { std::cout << "Load test..." << std::endl; - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); ValidateData(db, value1()); } @@ -1137,7 +1145,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest { std::cout << "Remove test..." << std::endl; - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.Remove(key(), id0())); EXPECT(db.Remove(key(), id1())); @@ -1148,7 +1156,7 @@ class DbMultiFileOperationsTest : public DbMultiFileTest { std::cout << "Remove record test..." << std::endl; - MultiFileDb db(temp_file, user_db_path); + MultiFileDb db(DbKinds::PerfDb, temp_file, user_db_path); EXPECT(db.Update(key(), id1(), value1())); EXPECT(db.Remove(key(), id1())); @@ -1179,7 +1187,9 @@ class DbMultiFileMultiThreadedReadTest : public DbMultiFileTest std::cout << "Initializing test data..." << std::endl; const std::string p = temp_file; const auto& up = user_db_path; - const auto c = [&p, up]() { return MultiFileDb(p, up); }; + const auto c = [&p, up]() { + return MultiFileDb(DbKinds::PerfDb, p, up); + }; ResetDb(); DBMultiThreadedTestWork::FillForReading(c); @@ -1224,7 +1234,9 @@ class DbMultiFileMultiThreadedTest : public DbMultiFileTest threads.reserve(DBMultiThreadedTestWork::threads_count); const std::string p = temp_file; const auto up = user_db_path; - const auto c = [&p, &up]() { return MultiFileDb(p, up); }; + const auto c = [&p, &up]() { + return MultiFileDb(DbKinds::PerfDb, p, up); + }; { std::unique_lock lock(mutex); diff --git a/test/tensor_cast.cpp b/test/tensor_cast.cpp index 109cafa0e3..a170d9da12 100644 --- a/test/tensor_cast.cpp +++ b/test/tensor_cast.cpp @@ -184,10 +184,10 @@ struct tensor_cast_driver : test_driver std::vector srcSuperStrides = srcSuper.desc.GetStrides(); std::vector dstSuperStrides = dstSuper.desc.GetStrides(); std::vector src_super_strides(srcSuperStrides.begin() + - (srcSuper.desc.GetSize() - castLens.size()), + (srcSuper.desc.GetNumDims() - castLens.size()), srcSuperStrides.end()); std::vector dst_super_strides(dstSuperStrides.begin() + - (dstSuper.desc.GetSize() - castLens.size()), + (dstSuper.desc.GetNumDims() - castLens.size()), dstSuperStrides.end()); srcDesc = miopen::TensorDescriptor(miopenInt32, castLens, src_super_strides); diff --git a/test/tensor_copy.cpp b/test/tensor_copy.cpp index ab36593905..9cdf762b52 100644 --- a/test/tensor_copy.cpp +++ b/test/tensor_copy.cpp @@ -163,10 +163,10 @@ struct tensor_copy_driver : test_driver std::vector srcSuperStrides = srcSuper.desc.GetStrides(); std::vector dstSuperStrides = dstSuper.desc.GetStrides(); std::vector src_super_strides(srcSuperStrides.begin() + - (srcSuper.desc.GetSize() - copyLens.size()), + (srcSuper.desc.GetNumDims() - copyLens.size()), srcSuperStrides.end()); std::vector dst_super_strides(dstSuperStrides.begin() + - (dstSuper.desc.GetSize() - copyLens.size()), + (dstSuper.desc.GetNumDims() - copyLens.size()), dstSuperStrides.end()); srcDesc = miopen::TensorDescriptor(this->type, copyLens, src_super_strides); diff --git a/test/tensor_holder.hpp b/test/tensor_holder.hpp index e055ec95b3..0e7ff7548f 100644 --- a/test/tensor_holder.hpp +++ b/test/tensor_holder.hpp @@ -37,11 +37,7 @@ #include "serialize.hpp" -#if !defined(_WIN32) #include -#else -#include -#endif using half = half_float::half; using hip_bfloat16 = bfloat16; #include @@ -187,7 +183,12 @@ struct tensor tensor(miopen::TensorDescriptor rhs) : desc(std::move(rhs)) { - assert(desc.GetType() == miopen_type{}); + assert(desc.GetType() == miopen_type{} + /// In the driver, T is input tensor type, but output tensor holders + /// are instantiatied with T as well. This leads to false assertion + /// failures when T is INT8 because output type is different. + /// \todo Get rid of this hack when the driver is improved: + || (miopen_type{} == miopenInt8 && desc.GetType() == miopenInt32)); data.resize(desc.GetElementSpace()); } diff --git a/test/tensor_layout.hpp b/test/tensor_layout.hpp deleted file mode 100644 index 65d014d936..0000000000 --- a/test/tensor_layout.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * - * MIT License - * - * Copyright (c) 2021 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. - * - *******************************************************************************/ -#ifndef GUARD_TENSOR_LAYOUT_HPP -#define GUARD_TENSOR_LAYOUT_HPP - -#include -#include -#include -#include -#include -#include - -template -void tensor_layout_to_strides(const std::vector& len, - const std::string& len_layout, - const std::string& layout, - std::vector& strides) -{ - // Bind the layout and the dimension lengths together into a map. - std::map dim_to_len; - std::transform(len.begin(), - len.end(), - len_layout.begin(), - std::inserter(dim_to_len, dim_to_len.end()), - [](T l, char dim) { return std::make_pair(dim, l); }); - - // Now construct the strides according to layout by multiply the - // dimension lengths together. - std::transform(len_layout.begin(), - len_layout.end(), - std::back_inserter(strides), - [&layout, &dim_to_len](char cur_layout_char) { - auto pos = layout.find(cur_layout_char); - if(pos == std::string::npos) - { - MIOPEN_THROW(std::string("mismatched layout string, unexpect char: ") - .append(1, cur_layout_char)); - } - return std::accumulate(layout.begin() + pos + 1, - layout.end(), - 1, - [&dim_to_len](T accumulator, char l) { - return accumulator * dim_to_len[l]; - }); - }); -} - -inline std::string tensor_layout_get_default(int size) -{ - if(size == 4) - return "NCHW"; - if(size == 5) - return "NCDHW"; - return ""; -} - -#endif diff --git a/test/tensor_scale.cpp b/test/tensor_scale.cpp index c1d079ad65..ff05bd9d1a 100644 --- a/test/tensor_scale.cpp +++ b/test/tensor_scale.cpp @@ -127,8 +127,8 @@ struct tensor_scale_driver : test_driver super = tensor{superLens}.generate(tensor_elem_gen_integer{max_value}); std::vector superStrides = super.desc.GetStrides(); - std::vector subStrides(superStrides.begin() + (super.desc.GetSize() - subLens.size()), - superStrides.end()); + std::vector subStrides( + superStrides.begin() + (super.desc.GetNumDims() - subLens.size()), superStrides.end()); subDesc = miopen::TensorDescriptor(this->type, subLens, subStrides); diff --git a/test/tensor_set.cpp b/test/tensor_set.cpp index bddc230196..5128617338 100644 --- a/test/tensor_set.cpp +++ b/test/tensor_set.cpp @@ -129,8 +129,8 @@ struct tensor_set_driver : test_driver super = tensor{superLens}.generate(tensor_elem_gen_integer{max_value}); std::vector superStrides = super.desc.GetStrides(); - std::vector subStrides(superStrides.begin() + (super.desc.GetSize() - subLens.size()), - superStrides.end()); + std::vector subStrides( + superStrides.begin() + (super.desc.GetNumDims() - subLens.size()), superStrides.end()); subDesc = miopen::TensorDescriptor(this->type, subLens, subStrides); diff --git a/test/tensor_transform.cpp b/test/tensor_transform.cpp index 3c222ccafe..90c101b9b0 100644 --- a/test/tensor_transform.cpp +++ b/test/tensor_transform.cpp @@ -436,10 +436,10 @@ struct tensor_transform_driver : test_driver std::vector superStrides_src = super_src.desc.GetStrides(); std::vector superStrides_dst = super_dst.desc.GetStrides(); std::vector subStrides_src(superStrides_src.begin() + - (super_src.desc.GetSize() - subLens.size()), + (super_src.desc.GetNumDims() - subLens.size()), superStrides_src.end()); std::vector subStrides_dst(superStrides_dst.begin() + - (super_dst.desc.GetSize() - subLens.size()), + (super_dst.desc.GetNumDims() - subLens.size()), superStrides_dst.end()); subDesc_src = miopen::TensorDescriptor(this->type, subLens, subStrides_src); diff --git a/test/tensor_util.hpp b/test/tensor_util.hpp index c5650ca4f0..40975c5d7e 100644 --- a/test/tensor_util.hpp +++ b/test/tensor_util.hpp @@ -27,11 +27,8 @@ #ifndef GUARD_TENSOR_UTIL_HPP #define GUARD_TENSOR_UTIL_HPP -#include #include #include -#include -#include #include "tensor_holder.hpp" // loop over sub-tensor, and operate on each data @@ -122,4 +119,66 @@ void output_tensor_to_bin(const char* fileName, T* data, size_t dataNumItems) } } +template +void print_tensor(const tensor& tensor_val, + std::string header_msg = "start", + size_t set_precision = 2) +{ + std::cout << "\n================= " << header_msg << " =====================\n"; + + const auto lens = tensor_val.desc.GetLengths(); + size_t dim = lens.size(); + if(dim == 2) + { + ford(lens[0], lens[1])([&](int ii, int jj) { + std::cout << std::fixed << std::setprecision(set_precision) << tensor_val(ii, jj) + << ", "; + if(jj == lens[1] - 1) + { + std::cout << "\n"; + } + }); + } + else if(dim == 3) + { + ford(lens[0], lens[1], lens[2])([&](int ii, int jj, int kk) { + std::cout << std::fixed << std::setprecision(set_precision) << tensor_val(ii, jj, kk) + << ", "; + if(kk == lens[2] - 1) + { + std::cout << "\n"; + } + if(kk == lens[2] - 1 && jj == lens[1] - 1) + { + std::cout << "\n"; + } + }); + } + else if(dim == 4) + { + ford(lens[0], lens[1], lens[2], lens[3])([&](int ii, int jj, int kk, int ll) { + std::cout << std::fixed << std::setprecision(set_precision) + << tensor_val(ii, jj, kk, ll) << ", "; + if(ll == lens[3] - 1) + { + std::cout << "\n"; + } + if(ll == lens[3] - 1 && kk == lens[2] - 1) + { + std::cout << "\n"; + } + if(ll == lens[3] - 1 && kk == lens[2] - 1 && jj == lens[1] - 1) + { + std::cout << "\n"; + } + }); + } + else + { + std::cerr << "Need to handle print for dim : " << dim << std::endl; + } + + std::cout << "\n=================end=====================\n"; +} + #endif diff --git a/test/test_perf.py b/test/test_perf.py index 0d4a432027..94038faeea 100755 --- a/test/test_perf.py +++ b/test/test_perf.py @@ -127,11 +127,11 @@ def run_driver_cmds(filename, install_path, override=None): cmd = f"export LD_LIBRARY_PATH={install_path}/lib && export MIOPEN_LOG_LEVEL=6 && "\ f"export MIOPEN_SYSTEM_DB_PATH={install_path}/share/miopen/db && "\ f"{var_str} "\ - f"{install_path}/bin/{driver_cmd} -V 0 -i 10 -w 1 -t 1" + f"{install_path}/bin/{driver_cmd} -V 0 -i 10 -w 1 -t 1 -G 1" else: cmd = f"export LD_LIBRARY_PATH={install_path}/lib && export MIOPEN_LOG_LEVEL=6 && "\ f"export MIOPEN_SYSTEM_DB_PATH={install_path}/share/miopen/db && "\ - f"{install_path}/bin/{driver_cmd} -V 0 -i 10 -w 1 -t 1" + f"{install_path}/bin/{driver_cmd} -V 0 -i 10 -w 1 -t 1 -G 1" print(f'Running cm: {cmd}') proc = subprocess.Popen(cmd, shell=True, diff --git a/tools/sqlite2txt/CMakeLists.txt b/tools/sqlite2txt/CMakeLists.txt new file mode 100644 index 0000000000..3b3ba47801 --- /dev/null +++ b/tools/sqlite2txt/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(sqlite2txt + main.cpp +) + +target_link_libraries(sqlite2txt SQLite::SQLite3 Threads::Threads) + +if (NOT WIN32) + target_link_libraries(sqlite2txt dl) +endif() + +clang_tidy_check(sqlite2txt) diff --git a/tools/sqlite2txt/main.cpp b/tools/sqlite2txt/main.cpp new file mode 100644 index 0000000000..00b5b66e76 --- /dev/null +++ b/tools/sqlite2txt/main.cpp @@ -0,0 +1,189 @@ +#include + +#include +#include +#include +#include +#include +#include + +std::unique_ptr OpenDb(const char* filename, int flags) +{ + sqlite3* db; + if(sqlite3_open_v2(filename, &db, flags, nullptr) != SQLITE_OK) + abort(); + if(db == nullptr) + abort(); + return {db, &sqlite3_close_v2}; +} + +std::unique_ptr PrepareStatement(sqlite3* db, + const std::string& sql) +{ + sqlite3_stmt* stmt; + const char* tail; + if(sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt, &tail) != SQLITE_OK || + stmt == nullptr) + { + std::cerr << "Error while preparing SQL statement: " << sqlite3_errmsg(db) << std::endl; + std::cerr << "Statement: {" << sql << "}" << std::endl; + abort(); + } + if(tail != &sql[0] + sql.length()) + { + std::cerr << "Statement leftover: {" << tail << "}" << std::endl; + abort(); + } + return {stmt, &sqlite3_finalize}; +} + +struct ProblemConfig +{ + int64_t in_d, in_h, in_w; + int64_t fil_d, fil_h, fil_w; + int64_t pad_d, pad_h, pad_w; + int64_t conv_stride_d, conv_stride_h, conv_stride_w; + int64_t dilation_d, dilation_h, dilation_w; + int64_t spatial_dim, out_channels, in_channels, batchsize, group_count, bias; + std::string layout, data_type, direction; + + template + static void Visit(Self&& self, std::function f) + { + // The column names match the driver command line argument names + f(self.spatial_dim, "spatial_dim"); + f(self.in_channels, "in_channels"); + f(self.in_h, "in_h"); + f(self.in_w, "in_w"); + f(self.in_d, "in_d"); + f(self.fil_h, "fil_h"); + f(self.fil_w, "fil_w"); + f(self.fil_d, "fil_d"); + f(self.out_channels, "out_channels"); + f(self.batchsize, "batchsize"); + f(self.pad_h, "pad_h"); + f(self.pad_w, "pad_w"); + f(self.pad_d, "pad_d"); + f(self.conv_stride_h, "conv_stride_h"); + f(self.conv_stride_w, "conv_stride_w"); + f(self.conv_stride_d, "conv_stride_d"); + f(self.dilation_h, "dilation_h"); + f(self.dilation_w, "dilation_w"); + f(self.dilation_d, "dilation_d"); + f(self.bias, "bias"); + f(self.group_count, "group_count"); + } + + template + static void Visit(Self&& self, std::function f) + { + f(self.layout, "layout"); + f(self.data_type, "data_type"); + f(self.direction, "direction"); + } + + template + static void VisitAll(Self&& self, const Visitor& f) + { + Visit(std::forward(self), [&](int64_t& value, std::string name) { f(value, name); }); + Visit(std::forward(self), + [&](std::string& value, std::string name) { f(value, name); }); + } + + [[nodiscard]] static const std::string& GetFieldNames() + { + static const std::string value = []() { + std::ostringstream ss; + ProblemConfig::VisitAll(ProblemConfig{}, [&](auto&&, auto name) { + if(ss.tellp() != 0) + ss << ", "; + ss << name; + }); + return ss.str(); + }(); + return value; + } + + [[nodiscard]] std::string Serialize() + { + std::ostringstream ss; + ProblemConfig::VisitAll(*this, [&](auto&& value, auto&&) { + if(ss.tellp() != 0) + ss << "x"; + ss << value; + }); + return ss.str(); + } +}; + +int main(int argn, char** args) +{ + if(argn < 2 || argn > 3) + { + std::cerr << "Usage:" << std::endl; + std::cerr << args[0] << " input_path [output_path]" << std::endl; + std::cerr << "input_path - path to the input file, expected to be sqlite3 db." << std::endl; + std::cerr << "output_path - optional path to the output file. Existing file would be " + "replaced. Defaults to the input_path with .txt appended to the end" + << std::endl; + } + + const std::string in_filename = args[1]; + const std::string out_filename = argn > 2 ? args[2] : (in_filename + ".txt"); + constexpr const int db_flags = SQLITE_OPEN_READONLY; + + const auto select_query = "SELECT solver, params, " + ProblemConfig::GetFieldNames() + + " FROM perf_db " + "INNER JOIN config ON perf_db.config = config.id"; + + const auto db = OpenDb(in_filename.c_str(), db_flags); + const auto stmt = PrepareStatement(db.get(), select_query); + auto db_content = std::unordered_map{}; + + for(int step_result = sqlite3_step(stmt.get()); step_result != SQLITE_DONE; + step_result = sqlite3_step(stmt.get())) + { + if(step_result == SQLITE_BUSY) + { + sqlite3_sleep(10); + continue; + } + + if(step_result == SQLITE_ERROR) + { + std::cerr << sqlite3_errmsg(db.get()) << std::endl; + abort(); + } + + if(step_result == SQLITE_MISUSE) + abort(); + + int col = 0; + std::string solver = reinterpret_cast(sqlite3_column_text(stmt.get(), col++)); + std::string perfcgf = reinterpret_cast(sqlite3_column_text(stmt.get(), col++)); + ProblemConfig problem; + + ProblemConfig::VisitAll(problem, [&](auto& value, auto) { + if constexpr(std::is_convertible_v) + value = sqlite3_column_int(stmt.get(), col++); + else if constexpr(std::is_convertible_v) + value = reinterpret_cast(sqlite3_column_text(stmt.get(), col++)); + else + static_assert(false, "unsupported type"); + }); + + if(sqlite3_column_count(stmt.get()) != col) + abort(); + + auto& record = db_content[problem.Serialize()]; + if(!record.empty()) + record.append(";"); + record.append(solver).append(":").append(perfcgf); + } + + auto out = std::ofstream{out_filename}; + for(const auto& line : db_content) + out << line.first << "=" << line.second << std::endl; + + return 0; +}