onnx-1.7.0/0000775000000000000000000000000013655345213011210 5ustar rootrootonnx-1.7.0/.gitignore0000664000000000000000000000213213655345213013176 0ustar rootroot## General # Compiled Object files *.slo *.lo *.o *.cuo # Compiled Dynamic libraries *.so *.dylib *.pyd # Compiled Static libraries *.lai *.la *.a # Compiled protocol buffers *.pb.h *.pb.cc onnx/*_pb2.py onnx/*_pb.py onnx/*_pb2.pyi onnx/*_pb.pyi # Compiled python *.pyc # Compiled MATLAB *.mex* # IPython notebook checkpoints .ipynb_checkpoints # Editor temporaries *.swn *.swo *.swp *~ # Sublime Text settings *.sublime-workspace *.sublime-project # Eclipse Project settings *.*project .settings # QtCreator files *.user # PyCharm files .idea # Visual Studio Code files .vscode # OSX dir files .DS_Store ## ONNX # build, distribute, and bins (+ python proto bindings) build build_* .build_debug/* .build_release/* .setuptools-cmake-build/* # setup.py intermediates .eggs dist onnx.egg-info *.ninja .ninja_deps .ninja_log compile_commands.json # generated files onnx/version.py compile_commands.json # test generated files .cache .coverage onnx/examples/.coverage.nbval .pytest_cache test_report # autocomplete .ycm_extra_conf.py # test coverage data files *.gcov .mypy_cache virtualenv venv onnx-1.7.0/README.md0000664000000000000000000001551313655345213012474 0ustar rootroot

[![Build Status](https://img.shields.io/travis/onnx/onnx/master.svg?label=Linux)](https://travis-ci.org/onnx/onnx) [![Build Status](https://img.shields.io/azure-devops/build/onnx-pipelines/onnx/5?label=Windows&logo=Azure-Pipelines)](https://dev.azure.com/onnx-pipelines/onnx/_build/latest?definitionId=5&branchName=master) [![Build Status](https://img.shields.io/jenkins/s/http/powerci.osuosl.org/onnx-ppc64le-nightly-build.svg?label=Linux%20ppc64le)](http://powerci.osuosl.org/job/onnx-ppc64le-nightly-build/) [Open Neural Network Exchange (ONNX)](https://onnx.ai) is an open ecosystem that empowers AI developers to choose the right tools as their project evolves. ONNX provides an open source format for AI models, both deep learning and traditional ML. It defines an extensible computation graph model, as well as definitions of built-in operators and standard data types. Currently we focus on the capabilities needed for inferencing (scoring). ONNX is [widely supported](http://onnx.ai/supported-tools) and can be found in many frameworks, tools, and hardware. Enabling interoperability between different frameworks and streamlining the path from research to production helps increase the speed of innovation in the AI community. We invite the community to join us and further evolve ONNX. # Use ONNX * [Tutorials for creating ONNX models](https://github.com/onnx/tutorials). * [Pre-trained ONNX models](https://github.com/onnx/models) # Learn about the ONNX spec * [Overview](docs/Overview.md) * [ONNX intermediate representation spec](docs/IR.md) * [Versioning principles of the spec](docs/Versioning.md) * [Operators documentation](docs/Operators.md) * [Python API Overview](docs/PythonAPIOverview.md) # Programming utilities for working with ONNX Graphs * [Shape and Type Inference](docs/ShapeInference.md) * [Graph Optimization](docs/Optimizer.md) * [Opset Version Conversion](docs/VersionConverter.md) # Contribute ONNX is a [community project](community). We encourage you to join the effort and contribute feedback, ideas, and code. You can participate in the [SIGs](community/sigs.md) and [Working Groups](community/working-groups.md) to shape the future of ONNX. Check out our [contribution guide](https://github.com/onnx/onnx/blob/master/docs/CONTRIBUTING.md) to get started. If you think some operator should be added to ONNX specification, please read [this document](docs/AddNewOp.md). # Discuss We encourage you to open [Issues](https://github.com/onnx/onnx/issues), or use Gitter for more real-time discussion: [![Join the chat at https://gitter.im/onnx/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/onnx/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # Follow Us Stay up to date with the latest ONNX news. [[Facebook](https://www.facebook.com/onnxai/)] [[Twitter](https://twitter.com/onnxai)] # Installation ## Binaries A binary build of ONNX is available from [Conda](https://conda.io), in [conda-forge](https://conda-forge.org/): ``` conda install -c conda-forge onnx ``` ## Source ### Linux and MacOS You will need an install of protobuf and numpy to build ONNX. One easy way to get these dependencies is via [Anaconda](https://www.anaconda.com/download/): ``` # Use conda-forge protobuf, as default doesn't come with protoc conda install -c conda-forge protobuf numpy ``` You can then install ONNX from PyPi (Note: Set environment variable `ONNX_ML=1` for onnx-ml): ``` pip install onnx ``` You can also build and install ONNX locally from source code: ``` git clone https://github.com/onnx/onnx.git cd onnx git submodule update --init --recursive python setup.py install ``` Note: When installing in a non-Anaconda environment, make sure to install the Protobuf compiler before running the pip installation of onnx. For example, on Ubuntu: ``` sudo apt-get install protobuf-compiler libprotoc-dev pip install onnx ``` ### Windows When building on Windows it is highly recommended that you also build protobuf locally as a static library. The version distributed with conda-forge is a DLL and this is a conflict as ONNX expects it to be a static lib. #### Instructions to build protobuf and ONNX on windows Step 1 : Build protobuf locally ``` git clone https://github.com/protocolbuffers/protobuf.git cd protobuf git checkout 3.9.x cd cmake # Explicitly set -Dprotobuf_MSVC_STATIC_RUNTIME=OFF to make sure protobuf does not statically link to runtime library cmake -G "Visual Studio 15 2017 Win64" -Dprotobuf_MSVC_STATIC_RUNTIME=OFF -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX= msbuild protobuf.sln /m /p:Configuration=Release msbuild INSTALL.vcxproj /p:Configuration=Release ``` Step 2: Build ONNX ``` # Get ONNX git clone https://github.com/onnx/onnx.git cd onnx git submodule update --init --recursive # Set environment variables to find protobuf and turn off static linking of ONNX to runtime library. # Even better option is to add it to user\system PATH so this step can be performed only once. # For more details check https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library?view=vs-2017 set PATH=\bin;%PATH% set USE_MSVC_STATIC_RUNTIME=0 # Optional : Set environment variable `ONNX_ML=1` for onnx-ml # Build ONNX python setup.py install ``` If you do not want to build protobuf and instead want to use protobuf from conda forge then follow these instructions. However please note : This method is just added as a convenience for users and there is very limited support from ONNX team when using this method. #### Instructions to build ONNX on windows in anaconda environment ``` # Use conda-forge protobuf conda install -c conda-forge protobuf=3.9.2 numpy # Get ONNX git clone https://github.com/onnx/onnx.git cd onnx git submodule update --init --recursive # Set environment variable for ONNX to use protobuf shared lib set CMAKE_ARGS="-DONNX_USE_PROTOBUF_SHARED_LIBS=ON" # Build ONNX # Optional : Set environment variable `ONNX_ML=1` for onnx-ml python setup.py install ``` ## Verify Installation After installation, run ``` python -c "import onnx" ``` to verify it works. Note that this command does not work from a source checkout directory; in this case you'll see: ``` ModuleNotFoundError: No module named 'onnx.onnx_cpp2py_export' ``` Change into another directory to fix this error. # Testing ONNX uses [pytest](https://docs.pytest.org) as test driver. In order to run tests, first you need to install pytest: ``` pip install pytest nbval ``` After installing pytest, do ``` pytest ``` to run tests. # Development Check out [contributor guide](https://github.com/onnx/onnx/blob/master/docs/CONTRIBUTING.md) for instructions. # License [MIT License](LICENSE) # Code of Conduct [ONNX Open Source Code of Conduct](https://onnx.ai/codeofconduct.html) onnx-1.7.0/.gitmodules0000664000000000000000000000036013655345213013364 0ustar rootroot[submodule "third_party/pybind11"] path = third_party/pybind11 url = https://github.com/pybind/pybind11.git branch = master [submodule "third_party/benchmark"] path = third_party/benchmark url = https://github.com/google/benchmark.git onnx-1.7.0/cmake/0000775000000000000000000000000013655345213012270 5ustar rootrootonnx-1.7.0/cmake/ONNXConfig.cmake.in0000664000000000000000000000163313655345213015612 0ustar rootroot# - Config file for the ONNX package # It defines the following variable(s) # ONNX_INCLUDE_DIRS - include directories for FooBar # as well as ONNX targets for other cmake libraries to use. # library version information set(ONNX_VERSION "@ONNX_VERSION@") # import targets include ("${CMAKE_CURRENT_LIST_DIR}/ONNXTargets.cmake") # include directory. # # Newer versions of CMake set the INTERFACE_INCLUDE_DIRECTORIES property # of the imported targets. It is hence not necessary to add this path # manually to the include search path for targets which link to gflags. # The following lines are here for backward compatibility, in case one # would like to use the old-style include path. get_filename_component( CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) get_filename_component( _INSTALL_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) set(ONNX_INCLUDE_DIRS "${_INSTALL_PREFIX}/include") onnx-1.7.0/cmake/summary.cmake0000664000000000000000000000373613655345213015000 0ustar rootroot# Prints accumulated ONNX configuration summary function (onnx_print_configuration_summary) message(STATUS "") message(STATUS "******** Summary ********") message(STATUS " CMake version : ${CMAKE_VERSION}") message(STATUS " CMake command : ${CMAKE_COMMAND}") message(STATUS " System : ${CMAKE_SYSTEM_NAME}") message(STATUS " C++ compiler : ${CMAKE_CXX_COMPILER}") message(STATUS " C++ compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS " CXX flags : ${CMAKE_CXX_FLAGS}") message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") get_directory_property(tmp DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_DEFINITIONS) message(STATUS " Compile definitions : ${tmp}") message(STATUS " CMAKE_PREFIX_PATH : ${CMAKE_PREFIX_PATH}") message(STATUS " CMAKE_INSTALL_PREFIX : ${CMAKE_INSTALL_PREFIX}") message(STATUS " CMAKE_MODULE_PATH : ${CMAKE_MODULE_PATH}") message(STATUS "") message(STATUS " ONNX version : ${ONNX_VERSION}") message(STATUS " ONNX NAMESPACE : ${ONNX_NAMESPACE}") message(STATUS " ONNX_BUILD_TESTS : ${ONNX_BUILD_TESTS}") message(STATUS " ONNX_BUILD_BENCHMARKS : ${ONNX_BUILD_BENCHMARKS}") message(STATUS " ONNX_USE_LITE_PROTO : ${ONNX_USE_LITE_PROTO}") message(STATUS " ONNXIFI_DUMMY_BACKEND : ${ONNXIFI_DUMMY_BACKEND}") message(STATUS " ONNXIFI_ENABLE_EXT : ${ONNXIFI_ENABLE_EXT}") message(STATUS "") message(STATUS " Protobuf compiler : ${PROTOBUF_PROTOC_EXECUTABLE}") message(STATUS " Protobuf includes : ${PROTOBUF_INCLUDE_DIRS}") message(STATUS " Protobuf libraries : ${PROTOBUF_LIBRARIES}") message(STATUS " BUILD_ONNX_PYTHON : ${BUILD_ONNX_PYTHON}") if (${BUILD_ONNX_PYTHON}) message(STATUS " Python version : ${PY_VERSION}") message(STATUS " Python executable : ${PYTHON_EXECUTABLE}") message(STATUS " Python includes : ${PYTHON_INCLUDE_DIR}") endif() endfunction() onnx-1.7.0/cmake/ONNXConfigVersion.cmake.in0000664000000000000000000000057013655345213017157 0ustar rootrootset(PACKAGE_VERSION "@ONNX_VERSION@") # Check whether the requested PACKAGE_FIND_VERSION is compatible if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") set(PACKAGE_VERSION_COMPATIBLE FALSE) else() set(PACKAGE_VERSION_COMPATIBLE TRUE) if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") set(PACKAGE_VERSION_EXACT TRUE) endif() endif() onnx-1.7.0/cmake/unittest.cmake0000664000000000000000000000674413655345213015164 0ustar rootrootset(UT_NAME ${PROJECT_NAME}_gtests) set(ONNX_ROOT ${PROJECT_SOURCE_DIR}) set(ONNXIFI_TEST_DRIVER onnxifi_test_driver_gtests) include(${ONNX_ROOT}/cmake/Utils.cmake) find_package(Threads) set(${UT_NAME}_libs ${googletest_STATIC_LIBRARIES}) set(${ONNXIFI_TEST_DRIVER}_libs ${googletest_STATIC_LIBRARIES}) list(APPEND ${UT_NAME}_libs onnx) list(APPEND ${UT_NAME}_libs onnx_proto) list(APPEND ${UT_NAME}_libs onnxifi_loader) list(APPEND ${UT_NAME}_libs onnxifi) list(APPEND ${UT_NAME}_libs ${PROTOBUF_LIBRARIES}) list(APPEND ${ONNXIFI_TEST_DRIVER}_libs onnx) list(APPEND ${ONNXIFI_TEST_DRIVER}_libs onnx_proto) list(APPEND ${ONNXIFI_TEST_DRIVER}_libs onnxifi_loader) list(APPEND ${ONNXIFI_TEST_DRIVER}_libs ${PROTOBUF_LIBRARIES}) list(APPEND ${ONNXIFI_TEST_DRIVER}_libs onnxifi) file(GLOB_RECURSE ${UT_NAME}_src "${ONNX_ROOT}/onnx/test/cpp/*.cc") file(GLOB_RECURSE ${ONNXIFI_TEST_DRIVER}_src "${ONNX_ROOT}/onnx/backend/test/cpp/*.h" "${ONNX_ROOT}/onnx/backend/test/cpp/*.cc") function(AddTest) cmake_parse_arguments(_UT "" "TARGET" "LIBS;SOURCES" ${ARGN}) list(REMOVE_DUPLICATES _UT_LIBS) list(REMOVE_DUPLICATES _UT_SOURCES) add_executable(${_UT_TARGET} ${_UT_SOURCES}) add_dependencies(${_UT_TARGET} onnx onnx_proto googletest) target_include_directories(${_UT_TARGET} PUBLIC ${googletest_INCLUDE_DIRS} ${ONNX_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS} ${ONNX_ROOT} ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(${_UT_TARGET} ${_UT_LIBS} ${CMAKE_THREAD_LIBS_INIT}) if(TARGET protobuf::libprotobuf) target_link_libraries(${_UT_TARGET} protobuf::libprotobuf) else() target_link_libraries(${_UT_TARGET} ${PROTOBUF_LIBRARIES}) endif() if(WIN32) target_compile_options(${_UT_TARGET} PRIVATE /EHsc # exception handling - C++ may throw, # extern "C" will not ) add_msvc_runtime_flag(${_UT_TARGET}) endif() if(MSVC) target_compile_options(${_UT_TARGET} PRIVATE /wd4146 # unary minus operator applied to # unsigned type, result still # unsigned from include\google\protob # uf\wire_format_lite.h /wd4244 # 'argument': conversion from 'google:: # protobuf::uint64' to 'int', possible # loss of data /wd4267 # Conversion from 'size_t' to 'int', # possible loss of data /wd4996 # The second parameter is ignored. ) endif() set(TEST_ARGS) if(ONNX_GENERATE_TEST_REPORTS) # generate a report file next to the test program list( APPEND TEST_ARGS "--gtest_output=xml:$.$.results.xml>" ) endif() add_test(NAME ${_UT_TARGET} COMMAND ${_UT_TARGET} ${TEST_ARGS} WORKING_DIRECTORY $) endfunction(AddTest) addtest(TARGET ${UT_NAME} SOURCES ${${UT_NAME}_src} LIBS ${${UT_NAME}_libs}) addtest(TARGET ${ONNXIFI_TEST_DRIVER} SOURCES ${${ONNXIFI_TEST_DRIVER}_src} LIBS ${${ONNXIFI_TEST_DRIVER}_libs}) onnx-1.7.0/cmake/external/0000775000000000000000000000000013655345213014112 5ustar rootrootonnx-1.7.0/cmake/external/googletest.cmake0000664000000000000000000000244413655345213017274 0ustar rootrootinclude (ExternalProject) set(googletest_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest/googletest/include) set(googletest_URL https://github.com/google/googletest.git) set(googletest_BUILD ${CMAKE_CURRENT_BINARY_DIR}/googletest/) set(googletest_TAG e93da23920e5b6887d6a6a291c3a59f83f5b579e) #0fe96607d85cf3a25ac40da369db62bbee2939a5 if(WIN32) set(googletest_STATIC_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest/googletest/Release/gtest.lib) else() set(googletest_STATIC_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest/googletest/libgtest.a) endif() if(ONNX_USE_MSVC_STATIC_RUNTIME) set(ONNX_USE_MSVC_SHARED_RUNTIME OFF) else() set(ONNX_USE_MSVC_SHARED_RUNTIME ON) endif() ExternalProject_Add(googletest PREFIX googletest GIT_REPOSITORY ${googletest_URL} GIT_TAG ${googletest_TAG} DOWNLOAD_DIR "${DOWNLOAD_LOCATION}" BUILD_IN_SOURCE 1 BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release --target gtest INSTALL_COMMAND "" CMAKE_CACHE_ARGS -DCMAKE_BUILD_TYPE:STRING=Release -DBUILD_GMOCK:BOOL=OFF -DBUILD_GTEST:BOOL=ON -Dgtest_force_shared_crt:BOOL=${ONNX_USE_MSVC_SHARED_RUNTIME} BUILD_BYPRODUCTS ${googletest_STATIC_LIBRARIES} ) onnx-1.7.0/cmake/Utils.cmake0000664000000000000000000000225013655345213014371 0ustar rootroot# # Add MSVC RunTime Flag function(add_msvc_runtime_flag lib) if(${ONNX_USE_MSVC_STATIC_RUNTIME}) target_compile_options(${lib} PRIVATE $<$>:/MT> $<$:/MTd>) else() target_compile_options(${lib} PRIVATE $<$>:/MD> $<$:/MDd>) endif() endfunction() function(add_onnx_global_defines target) target_compile_definitions(${target} PUBLIC "ONNX_NAMESPACE=${ONNX_NAMESPACE}") if(ONNX_ML) target_compile_definitions(${target} PUBLIC "ONNX_ML=1") endif() if(ONNX_USE_LITE_PROTO) target_compile_definitions(${target} PUBLIC "ONNX_USE_LITE_PROTO=1") endif() endfunction() function(add_whole_archive_flag lib output_var) if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(${output_var} -Wl,-force_load,$ PARENT_SCOPE) elseif(MSVC) # In MSVC, we will add whole archive in default. set(${output_var} -WHOLEARCHIVE:$> PARENT_SCOPE) else() # Assume everything else is like gcc set(${output_var} "-Wl,--whole-archive $ -Wl,--no-whole-archive" PARENT_SCOPE) endif() endfunction() onnx-1.7.0/setup.py0000664000000000000000000002705113655345213012727 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from distutils.spawn import find_executable from distutils import sysconfig, log import setuptools import setuptools.command.build_py import setuptools.command.develop import setuptools.command.build_ext from collections import namedtuple from contextlib import contextmanager import glob import os import shlex import subprocess import sys import platform from textwrap import dedent import multiprocessing TOP_DIR = os.path.realpath(os.path.dirname(__file__)) SRC_DIR = os.path.join(TOP_DIR, 'onnx') TP_DIR = os.path.join(TOP_DIR, 'third_party') CMAKE_BUILD_DIR = os.path.join(TOP_DIR, '.setuptools-cmake-build') WINDOWS = (os.name == 'nt') CMAKE = find_executable('cmake3') or find_executable('cmake') MAKE = find_executable('make') install_requires = [] setup_requires = [] tests_require = [] extras_require = {} ################################################################################ # Global variables for controlling the build variant ################################################################################ # Default value is set to TRUE\1 to keep the settings same as the current ones. # However going forward the recomemded way to is to set this to False\0 USE_MSVC_STATIC_RUNTIME = bool(os.getenv('USE_MSVC_STATIC_RUNTIME', '1') == '1') ONNX_ML = not bool(os.getenv('ONNX_ML') == '0') ONNX_VERIFY_PROTO3 = bool(os.getenv('ONNX_VERIFY_PROTO3') == '1') ONNX_NAMESPACE = os.getenv('ONNX_NAMESPACE', 'onnx') ONNX_BUILD_TESTS = bool(os.getenv('ONNX_BUILD_TESTS') == '1') DEBUG = bool(os.getenv('DEBUG')) COVERAGE = bool(os.getenv('COVERAGE')) ################################################################################ # Version ################################################################################ try: git_version = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=TOP_DIR).decode('ascii').strip() except (OSError, subprocess.CalledProcessError): git_version = None with open(os.path.join(TOP_DIR, 'VERSION_NUMBER')) as version_file: VersionInfo = namedtuple('VersionInfo', ['version', 'git_version'])( version=version_file.read().strip(), git_version=git_version ) ################################################################################ # Pre Check ################################################################################ assert CMAKE, 'Could not find "cmake" executable!' ################################################################################ # Utilities ################################################################################ @contextmanager def cd(path): if not os.path.isabs(path): raise RuntimeError('Can only cd to absolute path, got: {}'.format(path)) orig_path = os.getcwd() os.chdir(path) try: yield finally: os.chdir(orig_path) ################################################################################ # Customized commands ################################################################################ class ONNXCommand(setuptools.Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass class create_version(ONNXCommand): def run(self): with open(os.path.join(SRC_DIR, 'version.py'), 'w') as f: f.write(dedent('''\ # This file is generated by setup.py. DO NOT EDIT! from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals version = '{version}' git_version = '{git_version}' '''.format(**dict(VersionInfo._asdict())))) class cmake_build(setuptools.Command): """ Compiles everything when `python setupmnm.py build` is run using cmake. Custom args can be passed to cmake by specifying the `CMAKE_ARGS` environment variable. The number of CPUs used by `make` can be specified by passing `-j` to `setup.py build`. By default all CPUs are used. """ user_options = [ (str('jobs='), str('j'), str('Specifies the number of jobs to use with make')) ] built = False def initialize_options(self): self.jobs = None def finalize_options(self): if sys.version_info[0] >= 3: self.set_undefined_options('build', ('parallel', 'jobs')) if self.jobs is None and os.getenv("MAX_JOBS") is not None: self.jobs = os.getenv("MAX_JOBS") self.jobs = multiprocessing.cpu_count() if self.jobs is None else int(self.jobs) def run(self): if cmake_build.built: return cmake_build.built = True if not os.path.exists(CMAKE_BUILD_DIR): os.makedirs(CMAKE_BUILD_DIR) with cd(CMAKE_BUILD_DIR): build_type = 'Release' # configure cmake_args = [ CMAKE, '-DPYTHON_INCLUDE_DIR={}'.format(sysconfig.get_python_inc()), '-DPYTHON_EXECUTABLE={}'.format(sys.executable), '-DBUILD_ONNX_PYTHON=ON', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '-DONNX_NAMESPACE={}'.format(ONNX_NAMESPACE), '-DPY_EXT_SUFFIX={}'.format(sysconfig.get_config_var('EXT_SUFFIX') or ''), ] if COVERAGE: cmake_args.append('-DONNX_COVERAGE=ON') if COVERAGE or DEBUG: # in order to get accurate coverage information, the # build needs to turn off optimizations build_type = 'Debug' cmake_args.append('-DCMAKE_BUILD_TYPE=%s' % build_type) if WINDOWS: cmake_args.extend([ # we need to link with libpython on windows, so # passing python version to window in order to # find python in cmake '-DPY_VERSION={}'.format('{0}.{1}'.format(*sys.version_info[:2])), ]) if USE_MSVC_STATIC_RUNTIME: cmake_args.append('-DONNX_USE_MSVC_STATIC_RUNTIME=ON') if platform.architecture()[0] == '64bit': cmake_args.extend(['-A', 'x64', '-T', 'host=x64']) else: cmake_args.extend(['-A', 'Win32', '-T', 'host=x86']) if ONNX_ML: cmake_args.append('-DONNX_ML=1') if ONNX_VERIFY_PROTO3: cmake_args.append('-DONNX_VERIFY_PROTO3=1') if ONNX_BUILD_TESTS: cmake_args.append('-DONNX_BUILD_TESTS=ON') if 'CMAKE_ARGS' in os.environ: extra_cmake_args = shlex.split(os.environ['CMAKE_ARGS']) # prevent crossfire with downstream scripts del os.environ['CMAKE_ARGS'] log.info('Extra cmake args: {}'.format(extra_cmake_args)) cmake_args.extend(extra_cmake_args) cmake_args.append(TOP_DIR) subprocess.check_call(cmake_args) build_args = [CMAKE, '--build', os.curdir] if WINDOWS: build_args.extend(['--config', build_type]) build_args.extend(['--', '/maxcpucount:{}'.format(self.jobs)]) else: build_args.extend(['--', '-j', str(self.jobs)]) subprocess.check_call(build_args) class build_py(setuptools.command.build_py.build_py): def run(self): self.run_command('create_version') self.run_command('cmake_build') generated_python_files = \ glob.glob(os.path.join(CMAKE_BUILD_DIR, 'onnx', '*.py')) + \ glob.glob(os.path.join(CMAKE_BUILD_DIR, 'onnx', '*.pyi')) for src in generated_python_files: dst = os.path.join( TOP_DIR, os.path.relpath(src, CMAKE_BUILD_DIR)) self.copy_file(src, dst) return setuptools.command.build_py.build_py.run(self) class develop(setuptools.command.develop.develop): def run(self): self.run_command('build_py') setuptools.command.develop.develop.run(self) class build_ext(setuptools.command.build_ext.build_ext): def run(self): self.run_command('cmake_build') setuptools.command.build_ext.build_ext.run(self) def build_extensions(self): for ext in self.extensions: fullname = self.get_ext_fullname(ext.name) filename = os.path.basename(self.get_ext_filename(fullname)) lib_path = CMAKE_BUILD_DIR if os.name == 'nt': debug_lib_dir = os.path.join(lib_path, "Debug") release_lib_dir = os.path.join(lib_path, "Release") if os.path.exists(debug_lib_dir): lib_path = debug_lib_dir elif os.path.exists(release_lib_dir): lib_path = release_lib_dir src = os.path.join(lib_path, filename) dst = os.path.join(os.path.realpath(self.build_lib), "onnx", filename) self.copy_file(src, dst) class mypy_type_check(ONNXCommand): description = 'Run MyPy type checker' def run(self): """Run command.""" onnx_script = os.path.realpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "tools/mypy-onnx.py")) returncode = subprocess.call([sys.executable, onnx_script]) sys.exit(returncode) cmdclass = { 'create_version': create_version, 'cmake_build': cmake_build, 'build_py': build_py, 'develop': develop, 'build_ext': build_ext, 'typecheck': mypy_type_check, } ################################################################################ # Extensions ################################################################################ ext_modules = [ setuptools.Extension( name=str('onnx.onnx_cpp2py_export'), sources=[]) ] ################################################################################ # Packages ################################################################################ # no need to do fancy stuff so far packages = setuptools.find_packages() install_requires.extend([ 'protobuf', 'numpy', 'six', 'typing>=3.6.4; python_version < "3.5"', 'typing-extensions>=3.6.2.1', ]) ################################################################################ # Test ################################################################################ setup_requires.append('pytest-runner') tests_require.append('pytest') tests_require.append('nbval') tests_require.append('tabulate') if sys.version_info[0] == 3: # Mypy doesn't work with Python 2 extras_require['mypy'] = ['mypy==0.600'] ################################################################################ # Final ################################################################################ setuptools.setup( name="onnx", version=VersionInfo.version, description="Open Neural Network Exchange", ext_modules=ext_modules, cmdclass=cmdclass, packages=packages, include_package_data=True, install_requires=install_requires, setup_requires=setup_requires, tests_require=tests_require, extras_require=extras_require, author='bddppq', author_email='jbai@fb.com', url='https://github.com/onnx/onnx', entry_points={ 'console_scripts': [ 'check-model = onnx.bin.checker:check_model', 'check-node = onnx.bin.checker:check_node', 'backend-test-tools = onnx.backend.test.cmd_tools:main', ] }, ) onnx-1.7.0/MANIFEST.in0000664000000000000000000000041313655345213012744 0ustar rootrootrecursive-include onnx *.h *.c *.cc *.proto recursive-include third_party * include LICENSE recursive-include onnx/backend/test/data * recursive-include onnx/examples * recursive-include cmake * recursive-include tools * include VERSION_NUMBER include CMakeLists.txt onnx-1.7.0/third_party/0000775000000000000000000000000013655345213013541 5ustar rootrootonnx-1.7.0/third_party/pybind11/0000775000000000000000000000000013655345213015170 5ustar rootrootonnx-1.7.0/third_party/benchmark/0000775000000000000000000000000013655345213015473 5ustar rootrootonnx-1.7.0/onnx/0000775000000000000000000000000013655345213012172 5ustar rootrootonnx-1.7.0/onnx/tools/0000775000000000000000000000000013655345213013332 5ustar rootrootonnx-1.7.0/onnx/tools/net_drawer.py0000664000000000000000000001175513655345213016047 0ustar rootroot# A library and utility for drawing ONNX nets. Most of this implementation has # been borrowed from the caffe2 implementation # https://github.com/caffe2/caffe2/blob/master/caffe2/python/net_drawer.py # # The script takes two required arguments: # -input: a path to a serialized ModelProto .pb file. # -output: a path to write a dot file representation of the graph # # Given this dot file representation, you can-for example-export this to svg # with the graphviz `dot` utility, like so: # # $ dot -Tsvg my_output.dot -o my_output.svg from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import argparse from collections import defaultdict import json from onnx import ModelProto, GraphProto, NodeProto import pydot # type: ignore from typing import Text, Any, Callable, Optional, Dict OP_STYLE = { 'shape': 'box', 'color': '#0F9D58', 'style': 'filled', 'fontcolor': '#FFFFFF' } BLOB_STYLE = {'shape': 'octagon'} _NodeProducer = Callable[[NodeProto, int], pydot.Node] def _escape_label(name): # type: (Text) -> Text # json.dumps is poor man's escaping return json.dumps(name) def _form_and_sanitize_docstring(s): # type: (Text) -> Text url = 'javascript:alert(' url += _escape_label(s).replace('"', '\'').replace('<', '').replace('>', '') url += ')' return url def GetOpNodeProducer(embed_docstring=False, **kwargs): # type: (bool, **Any) -> _NodeProducer def ReallyGetOpNode(op, op_id): # type: (NodeProto, int) -> pydot.Node if op.name: node_name = '%s/%s (op#%d)' % (op.name, op.op_type, op_id) else: node_name = '%s (op#%d)' % (op.op_type, op_id) for i, input in enumerate(op.input): node_name += '\n input' + str(i) + ' ' + input for i, output in enumerate(op.output): node_name += '\n output' + str(i) + ' ' + output node = pydot.Node(node_name, **kwargs) if embed_docstring: url = _form_and_sanitize_docstring(op.doc_string) node.set_URL(url) return node return ReallyGetOpNode def GetPydotGraph( graph, # type: GraphProto name=None, # type: Optional[Text] rankdir='LR', # type: Text node_producer=None, # type: Optional[_NodeProducer] embed_docstring=False, # type: bool ): # type: (...) -> pydot.Dot if node_producer is None: node_producer = GetOpNodeProducer(embed_docstring=embed_docstring, **OP_STYLE) pydot_graph = pydot.Dot(name, rankdir=rankdir) pydot_nodes = {} # type: Dict[Text, pydot.Node] pydot_node_counts = defaultdict(int) # type: Dict[Text, int] for op_id, op in enumerate(graph.node): op_node = node_producer(op, op_id) pydot_graph.add_node(op_node) for input_name in op.input: if input_name not in pydot_nodes: input_node = pydot.Node( _escape_label( input_name + str(pydot_node_counts[input_name])), label=_escape_label(input_name), **BLOB_STYLE ) pydot_nodes[input_name] = input_node else: input_node = pydot_nodes[input_name] pydot_graph.add_node(input_node) pydot_graph.add_edge(pydot.Edge(input_node, op_node)) for output_name in op.output: if output_name in pydot_nodes: pydot_node_counts[output_name] += 1 output_node = pydot.Node( _escape_label( output_name + str(pydot_node_counts[output_name])), label=_escape_label(output_name), **BLOB_STYLE ) pydot_nodes[output_name] = output_node pydot_graph.add_node(output_node) pydot_graph.add_edge(pydot.Edge(op_node, output_node)) return pydot_graph def main(): # type: () -> None parser = argparse.ArgumentParser(description="ONNX net drawer") parser.add_argument( "--input", type=Text, required=True, help="The input protobuf file.", ) parser.add_argument( "--output", type=Text, required=True, help="The output protobuf file.", ) parser.add_argument( "--rankdir", type=Text, default='LR', help="The rank direction of the pydot graph.", ) parser.add_argument( "--embed_docstring", action="store_true", help="Embed docstring as javascript alert. Useful for SVG format.", ) args = parser.parse_args() model = ModelProto() with open(args.input, 'rb') as fid: content = fid.read() model.ParseFromString(content) pydot_graph = GetPydotGraph( model.graph, name=model.graph.name, rankdir=args.rankdir, node_producer=GetOpNodeProducer( embed_docstring=args.embed_docstring, **OP_STYLE ), ) pydot_graph.write_dot(args.output) if __name__ == '__main__': main() onnx-1.7.0/onnx/tools/update_model_dims.py0000664000000000000000000000707613655345213017374 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from six import string_types from typing import Any, List, Text, Dict, Set from onnx import ModelProto, ValueInfoProto import onnx.checker def update_inputs_outputs_dims(model, input_dims, output_dims): # type: (ModelProto, Dict[Text, List[Any]], Dict[Text, List[Any]]) -> ModelProto """ This function updates the dimension sizes of the model's inputs and outputs to the values provided in input_dims and output_dims. if the dim value provided is negative, a unique dim_param will be set for that dimension. Example. if we have the following shape for inputs and outputs: shape(input_1) = ('b', 3, 'w', 'h') shape(input_2) = ('b', 4) and shape(output) = ('b', 'd', 5) The parameters can be provided as: input_dims = { "input_1": ['b', 3, 'w', 'h'], "input_2": ['b', 4], } output_dims = { "output": ['b', -1, 5] } Putting it together: model = onnx.load('model.onnx') updated_model = update_inputs_outputs_dims(model, input_dims, output_dims) onnx.save(updated_model, 'model.onnx') """ dim_param_set = set() # type: Set[Text] def init_dim_param_set(dim_param_set, value_infos): # type: (Set[Text], List[ValueInfoProto]) -> None for info in value_infos: shape = info.type.tensor_type.shape for dim in shape.dim: if dim.HasField('dim_param'): dim_param_set.add(dim.dim_param) # type: ignore init_dim_param_set(dim_param_set, model.graph.input) # type: ignore init_dim_param_set(dim_param_set, model.graph.output) # type: ignore init_dim_param_set(dim_param_set, model.graph.value_info) # type: ignore def update_dim(tensor, dim, j, name): # type: (ValueInfoProto, Any, int, Text) -> None dim_proto = tensor.type.tensor_type.shape.dim[j] if isinstance(dim, int): if dim >= 0: if dim_proto.HasField('dim_value') and dim_proto.dim_value != dim: raise ValueError('Unable to set dimension value to {} for axis {} of {}. Contradicts existing dimension value {}.' .format(dim, j, name, dim_proto.dim_value)) dim_proto.dim_value = dim else: generated_dim_param = name + '_' + str(j) if generated_dim_param in dim_param_set: raise ValueError('Unable to generate unique dim_param for axis {} of {}. Please manually provide a dim_param value.' .format(j, name)) dim_proto.dim_param = generated_dim_param elif isinstance(dim, string_types): dim_proto.dim_param = dim else: raise ValueError('Only int or str is accepted as dimension value, incorrect type: {}'.format(type(dim))) for input in model.graph.input: input_name = input.name input_dim_arr = input_dims[input_name] for j, dim in enumerate(input_dim_arr): update_dim(input, dim, j, input_name) for output in model.graph.output: output_name = output.name output_dim_arr = output_dims[output_name] for j, dim in enumerate(output_dim_arr): update_dim(output, dim, j, output_name) onnx.checker.check_model(model) return model onnx-1.7.0/onnx/tools/__init__.py0000664000000000000000000000000013655345213015431 0ustar rootrootonnx-1.7.0/onnx/checker.cc0000664000000000000000000006457213655345213014123 0ustar rootroot#include "onnx/checker.h" #include "onnx/defs/schema.h" #include "onnx/defs/tensor_proto_util.h" #include "onnx/proto_utils.h" #include "onnx/string_utils.h" #include #include #include namespace ONNX_NAMESPACE { namespace checker { #define enforce_has_field(proto, field) \ do { \ if (!proto.has_##field()) { \ fail_check( \ "Field '", #field, "' of ", #proto, " is required but missing."); \ } \ } while (0) #define enforce_has_repeated_field(proto, field) \ do { \ if (!proto.field##_size()) { \ fail_check("Repeated Field '", #field, "' is required but missing."); \ } \ } while (0) #define enforce_non_empty_field(proto, field) \ do { \ if (proto.field().empty()) { \ fail_check( \ "Field '", \ #field, \ "' of ", \ #proto, \ " is required to be non-empty."); \ } \ } while (0) void check_value_info( const ValueInfoProto& value_info, const CheckerContext& ctx) { enforce_non_empty_field(value_info, name); // Relax constraint for subgraph input/output. if (!ctx.is_main_graph()) return; enforce_has_field(value_info, type); const auto value_case = value_info.type().value_case(); switch (value_case) { case TypeProto::kTensorType: { const auto& type = value_info.type().tensor_type(); enforce_has_field(type, elem_type); enforce_has_field(type, shape); } break; case TypeProto::kSequenceType: { const auto& type = value_info.type().sequence_type(); enforce_has_field(type, elem_type); } break; case TypeProto::kMapType: { const auto& type = value_info.type().map_type(); enforce_has_field(type, key_type); enforce_has_field(type, value_type); } break; #ifdef ONNX_ML case TypeProto::kOpaqueType: break; case TypeProto::kSparseTensorType: { const auto& type = value_info.type().sparse_tensor_type(); enforce_has_field(type, elem_type); enforce_has_field(type, shape); } break; #endif default: fail_check( "Unrecognized type value case (value_info name: ", value_info.name(), "): ", value_case); } } void check_tensor(const TensorProto& tensor, const CheckerContext& ctx) { enforce_has_field(tensor, data_type); if (tensor.data_type() == TensorProto::UNDEFINED) { fail_check( "setting data_type field (tensor name: ", tensor.name(), ") to UNDEFINED is not allowed"); } int num_value_fields = 0; const char* value_field = nullptr; #define check_data_field(field) \ bool has_##field = tensor.field().size(); \ if (has_##field) { \ ++num_value_fields; \ value_field = #field; \ } check_data_field(float_data); check_data_field(int32_data); check_data_field(string_data); check_data_field(int64_data); check_data_field(raw_data); check_data_field(double_data); check_data_field(uint64_data); #undef check_data_field bool stored_externally = tensor.has_data_location() && tensor.data_location() == TensorProto::EXTERNAL; if (stored_externally) { if (num_value_fields != 0) { fail_check( "Data of TensorProto ( tensor name: ", tensor.name(), ") is stored externally and should not have data field.", value_field); } bool has_location = false; for (const StringStringEntryProto& entry : tensor.external_data()) { if (entry.has_key() && entry.has_value() && entry.key() == "location") { has_location = true; if (!std::ifstream(ctx.get_model_dir() + entry.value())) { fail_check( "Data of TensorProto ( tensor name: ", tensor.name(), ") should be stored in ", ctx.get_model_dir() + entry.value(), ", but it doesn't exist or is not accessible."); } } } if (!has_location) { fail_check( "TensorProto ( tensor name: ", tensor.name(), ") is stored externally but doesn't have a location."); } return; } int64_t nelem = 1; for (auto x : tensor.dims()) { nelem *= x; } if (nelem == 0 && num_value_fields != 0) { fail_check( "TensorProto (tensor name: ", tensor.name(), ") is 0-element but contains data!"); } if (nelem != 0 && num_value_fields != 1) { fail_check( "TensorProto (tensor name: ", tensor.name(), ") should contain one and only one value field."); } if (has_raw_data) { if (tensor.data_type() == TensorProto::STRING) { fail_check( "STRING data (tensor name: ", tensor.name(), ") should not be stored in raw_data field"); } return; } else { #define check_field(field) \ if (nelem != 0 && !has_##field) { \ fail_check( \ "values of data_type '", \ tensor.data_type(), \ "' should be stored in field '", \ #field, \ "' instead of '", \ value_field, \ "'"); \ } switch (tensor.data_type()) { case TensorProto::FLOAT: case TensorProto::COMPLEX64: check_field(float_data); break; case TensorProto::DOUBLE: case TensorProto::COMPLEX128: check_field(double_data); break; case TensorProto::INT32: case TensorProto::UINT8: case TensorProto::INT8: case TensorProto::UINT16: case TensorProto::INT16: case TensorProto::BOOL: case TensorProto::FLOAT16: case TensorProto::BFLOAT16: check_field(int32_data); break; case TensorProto::INT64: check_field(int64_data); break; case TensorProto::UINT32: case TensorProto::UINT64: check_field(uint64_data); break; case TensorProto::STRING: check_field(string_data); break; default: fail_check( "Unrecognized data_type (tensor name: ", tensor.name(), "): ", tensor.data_type()); } } #undef check_field } // Check that the index data stored in a SparseTensorProto is valid. // indices: a 1-dimensional tensor; indices[i] represents the // linearized index value for the i-th nonzero value. void check_sparse_tensor_indices_1( const TensorProto& indices, const SparseTensorProto& sparse_tensor_proto, size_t nnz) { int dense_rank = sparse_tensor_proto.dims_size(); int64_t dense_size = 1; for (int i = 0; i < dense_rank; ++i) dense_size *= sparse_tensor_proto.dims(i); if (static_cast(indices.dims(0)) != nnz) fail_check( "Sparse tensor indices (", indices.name(), ") has ", indices.dims(0), " values, but NNZ is ", nnz); // Check if indices appear in ascending order, and if they have valid // values. The i-th value in index_data is the linear index of the i-th // non-zero value. const std::vector index_data = ParseData(&indices); int64_t prev_index = -1; for (size_t i = 0; i < nnz; ++i) { int64_t curr_index = index_data[i]; // linearized index of i-th value if (curr_index < 0 || curr_index >= dense_size) fail_check( "Sparse tensor (", indices.name(), ") index value at position [", i, "] out of range [0, ", dense_size - 1, "]"); if (curr_index <= prev_index) { fail_check( "Sparse tensor (", indices.name(), ") index value at position [", i, "] not in sorted order."); } prev_index = curr_index; } } // Check that the index data stored in a SparseTensorProto is valid. // indices: a 2-dimensional tensor; indices[i,j] represents the j-th // index value for the i-th nonzero value. void check_sparse_tensor_indices_2( const TensorProto& indices, const SparseTensorProto& sparse_tensor_proto, size_t nnz) { int dense_rank = sparse_tensor_proto.dims_size(); if (static_cast(indices.dims(0)) != nnz) fail_check( "Sparse tensor indices (", indices.name(), ") first dimension size does not equal NNZ."); if (indices.dims(1) != dense_rank) fail_check( "Sparse tensor indices (", indices.name(), ") second dimension size does not match rank of tensor."); // Check if indices appear in ascending order, and if they have valid // values. const std::vector index_data = ParseData(&indices); int64_t prev_index = -1; for (size_t i = 0; i < nnz; ++i) { int64_t curr_index = 0; // linearized index of i-th value for (int j = 0; j < dense_rank; ++j) { auto index_ij = index_data[i * dense_rank + j]; if ((index_ij < 0) || (index_ij >= sparse_tensor_proto.dims(j))) fail_check( "Sparse tensor (", indices.name(), ") index value at position [", i, ",", j, "] out of range."); curr_index = curr_index * sparse_tensor_proto.dims(j) + index_ij; } if (curr_index <= prev_index) { fail_check( "Sparse tensor (", indices.name(), ") index value at position [", i, "] not in lexicographic sorted order."); } prev_index = curr_index; } } void check_sparse_tensor( const SparseTensorProto& sparse_tensor_proto, const CheckerContext& ctx) { enforce_has_field(sparse_tensor_proto, values); const TensorProto& values = sparse_tensor_proto.values(); check_tensor(values, ctx); // values must be a tensor of shape [NNZ] // Currently we restrict the value associated with a particular index-tuple // to be a single value. In the future, if there is a requirement, // we may extend this to permit the value to be a "sub-tensor", in which // case values will have dimension > 1. if (values.dims_size() != 1) fail_check("Sparse tensor values (", values.name(), ") must have rank 1."); size_t nnz = static_cast(values.dims(0)); int dense_rank = sparse_tensor_proto.dims_size(); if (dense_rank == 0) { // TODO: Should we add a name field for a sparse-tensor-proto? // Currently, values has a name, but message may be a bit confusing. fail_check( "Sparse tensor (", values.name(), ") must have a dense-rank > 0"); } for (int i = 0; i < dense_rank; ++i) { if (sparse_tensor_proto.dims(i) <= 0) fail_check( "Sparse tensor (", values.name(), ") dimensions are not positive."); } if (sparse_tensor_proto.has_indices()) { const TensorProto& indices = sparse_tensor_proto.indices(); check_tensor(indices, ctx); if (indices.data_type() != TensorProto::INT64) fail_check( "Sparse tensor indices (", indices.name(), ") must have INT64 type."); switch (indices.dims().size()) { case 1: // Indices in linearized format check_sparse_tensor_indices_1(indices, sparse_tensor_proto, nnz); return; case 2: // Check COO-style index. E.g., an index for a 3D tensor is a 3-tuple. check_sparse_tensor_indices_2(indices, sparse_tensor_proto, nnz); return; default: fail_check( "Sparse tensor indices (", indices.name(), ") must have rank 1 or 2."); } } else if (nnz != 0) fail_check("Sparse tensor (", values.name(), ") has no index values."); } // NB: This is a generic "attribute well-formedness" check, it doesn't // actually test if an attribute is valid per a schema void check_attribute( const AttributeProto& attr, const CheckerContext& ctx, const LexicalScopeContext& lex_ctx) { enforce_non_empty_field(attr, name); if (ctx.get_ir_version() >= 0x00000002) { enforce_has_field(attr, type); } int used_fields = 0; #define check_type(expected_type) \ if (attr.has_type() && attr.type() != expected_type) { \ fail_check( \ "type field and data field mismatch in attribute ", attr.name(), "."); \ } #define check_singular_field(field, type) \ if (attr.has_##field()) { \ ++used_fields; \ check_type(type); \ } #define check_repeated_field(field, type) \ if (attr.field##_size() > 0) { \ ++used_fields; \ check_type(type); \ } check_singular_field(f, AttributeProto::FLOAT); check_singular_field(i, AttributeProto::INT); check_singular_field(s, AttributeProto::STRING); check_singular_field(t, AttributeProto::TENSOR); check_singular_field(g, AttributeProto::GRAPH); check_singular_field(sparse_tensor, AttributeProto::SPARSE_TENSOR); check_repeated_field(floats, AttributeProto::FLOATS); check_repeated_field(ints, AttributeProto::INTS); check_repeated_field(strings, AttributeProto::STRINGS); check_repeated_field(tensors, AttributeProto::TENSORS); check_repeated_field(graphs, AttributeProto::GRAPHS); check_repeated_field(sparse_tensors, AttributeProto::SPARSE_TENSORS); #undef check_type #undef check_singular_field #undef check_repeated_field // Normally, used_fields is expected to be 1. // In proto3, when the value to be set is type default value (say 0 for // int), used_fields may be 0. if (used_fields > 1) { fail_check( "Attribute (name: ", attr.name(), ") should not contain more than one value field."); } if (!ctx.is_main_graph()) { // It's an attribute of a node in function body. if (attr.has_ref_attr_name() && used_fields != 0) { // The attribute proto is supposed to refer to data outside and does not // have its own value field set. fail_check( "Attribute (name: ", attr.name(), ") should refer to attribute in parent node."); } } if (attr.has_t()) { check_tensor(attr.t(), ctx); } if (attr.has_sparse_tensor()) { check_sparse_tensor(attr.sparse_tensor(), ctx); } if (attr.has_g()) { CheckerContext subgraph_ctx(ctx); subgraph_ctx.set_is_main_graph(false); check_graph(attr.g(), subgraph_ctx, lex_ctx); } for (const auto& tensor : attr.tensors()) { check_tensor(tensor, ctx); } for (const auto& sparse_tensor : attr.sparse_tensors()) { check_sparse_tensor(sparse_tensor, ctx); } if (attr.graphs().size() > 0) { CheckerContext subgraph_ctx(ctx); subgraph_ctx.set_is_main_graph(false); for (const auto& graph : attr.graphs()) { check_graph(graph, subgraph_ctx, lex_ctx); } } } void check_node( const NodeProto& node, const CheckerContext& ctx, const LexicalScopeContext& lex_ctx) { enforce_non_empty_field(node, op_type); if (node.input().empty() && node.output().empty()) { fail_check( "NodeProto (name: ", node.name(), ", type: ", node.op_type(), ") has zero input and zero output."); } // Put the removed experimental ops here static std::set experimental_ops = {"ATen", "Affine", "ConstantFill", "Crop", "DynamicSlice", "GRUUnit", "GivenTensorFill", "ImageScaler", "ParametricSoftplus", "Scale", "ScaledTanh"}; if (experimental_ops.count(node.op_type())) { std::cerr << "Warning: " << node.op_type() << " was a removed " << "experimental ops. In the future, we may directly " << "reject this operator. Please update your model as soon " << "as possible." << std::endl; return; } // Resolve domain for node const auto& opset_imports = ctx.get_opset_imports(); auto dit = opset_imports.find(node.domain()); if (dit == opset_imports.end()) { fail_check("No opset import for domain '" + node.domain() + "'"); } auto domain_version = dit->second; for (const auto& attr : node.attribute()) { check_attribute(attr, ctx, lex_ctx); } const auto* schema = ctx.get_schema_registry()->GetSchema( node.op_type(), domain_version, node.domain()); if (!schema) { if (node.domain() == ONNX_DOMAIN || node.domain() == AI_ONNX_ML_DOMAIN || node.domain() == "ai.onnx" || node.domain() == AI_ONNX_TRAINING_DOMAIN) { // fail the checker if op in built-in domains has no schema fail_check( "No Op registered for " + node.op_type() + " with domain_version of " + ONNX_NAMESPACE::to_string(domain_version)); } else { // TODO: expose the registration of the op schemas appropriately in // python, so we can load and register operators in other domains // // before we complete the above todo, let's skip the schema check for // now } } else if (schema->Deprecated()) { fail_check( "Op registered for " + node.op_type() + " is deprecated in domain_version of " + ONNX_NAMESPACE::to_string(domain_version)); } else { schema->Verify(node); } } void check_graph( const GraphProto& graph, const CheckerContext& ctx, const LexicalScopeContext& parent_lex) { enforce_non_empty_field(graph, name); for (const auto& value_info : graph.input()) { check_value_info(value_info, ctx); } for (const auto& value_info : graph.output()) { check_value_info(value_info, ctx); } // Inherit values available in outer scope // Note that we do not allow shadowing, so the presence of an already-defined // name is always an error. LexicalScopeContext lex_ctx{parent_lex}; for (const auto& value_info : graph.input()) { // TODO: If shadowing isn't allowed, this should maybe use // this_or_ancestor_graph_has if (lex_ctx.this_graph_has(value_info.name())) { fail_check( "Graph must be in single static assignment (SSA) form, however '", value_info.name(), "' has been used as graph input names multiple times."); } lex_ctx.add(value_info.name()); } for (const auto& init : graph.initializer()) { if (ctx.get_ir_version() <= 0x00000003) { // Initializers are a subset of graph inputs for IR_VERSION <= 3 if (!lex_ctx.this_graph_has(init.name())) { fail_check(init.name() + " in initializer but not in graph input"); } } else { // An initializer is allowed to have the same name as an input, // but is not required to (for IR_VERSION >= 4) lex_ctx.add(init.name()); } check_tensor(init, ctx); } // TODO: Need to check that sparse-initializers names are distinct from // initializer names. It looks like the existing checker does not check for // certain duplication of names: e.g., two entries in the initializer list // with same name. Will add a new integrated check. for (const auto& sparse_init : graph.sparse_initializer()) { check_sparse_tensor(sparse_init, ctx); lex_ctx.add(sparse_init.values().name()); } for (const auto& node : graph.node()) { // nodes must be in topologically sorted order for (const auto& input : node.input()) { // explicit optional input if (input.empty()) { continue; } if (!lex_ctx.this_or_ancestor_graph_has(input)) { fail_check( "Nodes in a graph must be topologically sorted, however input '", input, "' of node: \n", ProtoDebugString(node), "\n is not output of any previous nodes."); } } // This needs to happen before SSA check since we don't want to recurse and // find that outputs from control flow ops are colliding with names in the // inner block try { check_node(node, ctx, lex_ctx); } catch (ValidationError& ex) { ex.AppendContext("Bad node spec: " + ProtoDebugString(node)); throw ex; } // check for SSA form for (const auto& output : node.output()) { // optional output if (output.empty()) { continue; } if (lex_ctx.this_or_ancestor_graph_has(output)) { fail_check( "Graph must be in single static assignment (SSA) form, however '", output, "' has been used as output names multiple times."); } lex_ctx.add(output); } } } void check_function( const FunctionProto& function, const CheckerContext& ctx, const LexicalScopeContext& parent_lex) { enforce_non_empty_field(function, name); enforce_has_field(function, since_version); CheckerContext ctx_copy = ctx; std::unordered_map opsets; for (auto& relied_opset : function.opset_import()) { auto& domain = relied_opset.domain(); auto version = relied_opset.version(); opsets[domain] = static_cast(version); } ctx_copy.set_opset_imports(opsets); LexicalScopeContext lex_ctx{parent_lex}; for (const auto& input : function.input()) { // TODO: If shadowing isn't allowed, this should maybe use // this_or_ancestor_graph_has if (lex_ctx.this_graph_has(input)) { fail_check( "Graph must be in single static assignment (SSA) form, however '", input, "' has been used multiple times."); } lex_ctx.add(input); } std::unordered_set outputs; for (const auto& output : function.output()) { auto result = outputs.insert(output); if (!result.second) { fail_check( "function (", function.name(), ") should not have duplicate outputs specified."); } } std::unordered_set attrs; for (const auto& attr : function.attribute()) { auto result = attrs.insert(attr); if (!result.second) { fail_check( "function (", function.name(), ") should not have duplicate attributes specified."); } } for (const auto& node : function.node()) { // nodes must be in topologically sorted order for (const auto& input : node.input()) { // explicit optional input if (input.empty()) { continue; } if (!lex_ctx.this_graph_has(input)) { fail_check( "Nodes in a function must be topologically sorted, however input '", input, "' of node: \n", ProtoDebugString(node), "\n is neither output of any previous nodes nor input of the function."); } } check_node(node, ctx_copy, lex_ctx); // check for SSA form for (const auto& output : node.output()) { // optional output if (output.empty()) { continue; } if (lex_ctx.this_or_ancestor_graph_has(output)) { fail_check( "Function must be in single static assignment (SSA) form, however '", output, "' has been used as output names multiple times."); } lex_ctx.add(output); } } } void check_model(const ModelProto& model, CheckerContext& ctx) { if (!model.ir_version()) { fail_check("The model does not have an ir_version set properly."); } if (model.ir_version() > IR_VERSION) { fail_check("Your model ir_version is higher than the checker's."); } if (model.metadata_props_size() > 1) { std::unordered_set keys; for (const StringStringEntryProto& entry : model.metadata_props()) { auto i = keys.insert(entry.key()); if (!i.second) { fail_check("Your model has duplicate keys in metadata_props."); } } } std::unordered_map versions; ctx.set_ir_version(static_cast(model.ir_version())); std::unordered_map opset_imports; for (const auto& opset_import : model.opset_import()) { opset_imports[opset_import.domain()] = static_cast(opset_import.version()); } if (model.ir_version() >= 3) { if (opset_imports.empty()) fail_check( "model with IR version >= 3 must specify opset_import for ONNX"); } else { if (opset_imports.empty()) opset_imports[ONNX_DOMAIN] = 1; else fail_check( "model with IR version < 3 cannot have opset_import specified"); } ctx.set_opset_imports(opset_imports); LexicalScopeContext lex_ctx; check_graph(model.graph(), ctx, lex_ctx); } void check_model(const std::string& model_path) { ModelProto model; std::fstream model_stream(model_path, std::ios::in | std::ios::binary); if (!model_stream.good()) { fail_check( "Unable to open model file:", model_path, ". Please check if it is a valid file."); } std::string data{std::istreambuf_iterator{model_stream}, std::istreambuf_iterator{}}; if (!ParseProtoFromBytes(&model, data.c_str(), data.size())) { fail_check( "Unable to parse model from file:", model_path, ". Please check if it is a valid protobuf file of model."); } CheckerContext ctx; std::string model_dir; size_t pos = model_path.find_last_of("\\/"); if (pos != std::string::npos) { model_dir = model_path.substr(0, pos + 1); } ctx.set_model_dir(model_dir); check_model(model, ctx); } void check_model(const ModelProto& model) { CheckerContext ctx; check_model(model, ctx); } #undef fail_check #undef enforce_has_field #undef enforce_has_repeated_field #undef enforce_non_empty_field } // namespace checker } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/mapping.py0000664000000000000000000000470313655345213014203 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from onnx import TensorProto from typing import Text, Any import numpy as np # type: ignore TENSOR_TYPE_TO_NP_TYPE = { int(TensorProto.FLOAT): np.dtype('float32'), int(TensorProto.UINT8): np.dtype('uint8'), int(TensorProto.INT8): np.dtype('int8'), int(TensorProto.UINT16): np.dtype('uint16'), int(TensorProto.INT16): np.dtype('int16'), int(TensorProto.INT32): np.dtype('int32'), int(TensorProto.INT64): np.dtype('int64'), int(TensorProto.BOOL): np.dtype('bool'), int(TensorProto.FLOAT16): np.dtype('float16'), int(TensorProto.DOUBLE): np.dtype('float64'), int(TensorProto.COMPLEX64): np.dtype('complex64'), int(TensorProto.COMPLEX128): np.dtype('complex128'), int(TensorProto.UINT32): np.dtype('uint32'), int(TensorProto.UINT64): np.dtype('uint64'), int(TensorProto.STRING): np.dtype(np.object) } NP_TYPE_TO_TENSOR_TYPE = {v: k for k, v in TENSOR_TYPE_TO_NP_TYPE.items()} TENSOR_TYPE_TO_STORAGE_TENSOR_TYPE = { int(TensorProto.FLOAT): int(TensorProto.FLOAT), int(TensorProto.UINT8): int(TensorProto.INT32), int(TensorProto.INT8): int(TensorProto.INT32), int(TensorProto.UINT16): int(TensorProto.INT32), int(TensorProto.INT16): int(TensorProto.INT32), int(TensorProto.INT32): int(TensorProto.INT32), int(TensorProto.INT64): int(TensorProto.INT64), int(TensorProto.BOOL): int(TensorProto.INT32), int(TensorProto.FLOAT16): int(TensorProto.UINT16), int(TensorProto.BFLOAT16): int(TensorProto.UINT16), int(TensorProto.DOUBLE): int(TensorProto.DOUBLE), int(TensorProto.COMPLEX64): int(TensorProto.FLOAT), int(TensorProto.COMPLEX128): int(TensorProto.DOUBLE), int(TensorProto.UINT32): int(TensorProto.UINT32), int(TensorProto.UINT64): int(TensorProto.UINT64), int(TensorProto.STRING): int(TensorProto.STRING), } STORAGE_TENSOR_TYPE_TO_FIELD = { int(TensorProto.FLOAT): 'float_data', int(TensorProto.INT32): 'int32_data', int(TensorProto.INT64): 'int64_data', int(TensorProto.UINT16): 'int32_data', int(TensorProto.DOUBLE): 'double_data', int(TensorProto.COMPLEX64): 'float_data', int(TensorProto.COMPLEX128): 'double_data', int(TensorProto.UINT32): 'uint64_data', int(TensorProto.UINT64): 'uint64_data', int(TensorProto.STRING): 'string_data', int(TensorProto.BOOL): 'int32_data', } onnx-1.7.0/onnx/onnx-operators_pb.h0000664000000000000000000000033613655345213016024 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/onnx_pb.h" #ifdef ONNX_ML #include "onnx/onnx-operators-ml.pb.h" #else #include "onnx/onnx-operators.pb.h" #endifonnx-1.7.0/onnx/onnxifi_wrapper.c0000664000000000000000000007001213655345213015550 0ustar rootroot/* * ONNX wrapper api discovers vendor-specific implementations installed in * the system and exposes them under a single interface. * * * */ #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #endif #include #include #if defined(_WIN32) #define ONNXIFI_FILENAME_WILDCARD L"\\onnxifi-*.dll" #define ONNXIFI_FILENAME_WILDCARD_LENGTH 14 #elif defined(__APPLE__) /* Minimum filename: "libonnxifi-?.dylib" */ #define ONNXIFI_FILENAME_MIN 18 #define ONNXIFI_FILENAME_PREFIX "libonnxifi-" #define ONNXIFI_FILENAME_SUFFIX ".dylib" #else /* Minimum filename: "libonnxifi-?.so" */ #define ONNXIFI_FILENAME_MIN 15 #define ONNXIFI_FILENAME_PREFIX "libonnxifi-" #define ONNXIFI_FILENAME_SUFFIX ".so" #endif #define ONNXIFI_BACKEND_ID_MAGIC UINT32_C(0x2EDD3764) #define ONNXIFI_BACKEND_MAGIC UINT32_C(0x4B9B2902) #define ONNXIFI_GRAPH_MAGIC UINT32_C(0xD9ACFACD) #define ONNXIFI_EVENT_MAGIC UINT32_C(0x18C1D735) struct onnxifi_backend_id_wrapper { uint32_t magic; onnxBackendID backend_id; struct onnxifi_library* library; }; struct onnxifi_backend_wrapper { uint32_t magic; onnxBackend backend; struct onnxifi_library* library; }; struct onnxifi_graph_wrapper { uint32_t magic; onnxGraph graph; struct onnxifi_library* library; }; struct onnxifi_event_wrapper { uint32_t magic; onnxEvent event; struct onnxifi_library* library; }; static struct onnxifi_library* libraries = NULL; static uint32_t num_libraries = 0; #ifdef _WIN32 static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; static BOOL CALLBACK load_all_windows_backends( PINIT_ONCE init_once, PVOID parameter, PVOID* context) { WCHAR* onnxifi_library_wildcard = NULL; HANDLE find_file_handle = INVALID_HANDLE_VALUE; WIN32_FIND_DATAW find_file_data; UINT system_directory_path_length = GetSystemDirectoryW(NULL, 0); if (system_directory_path_length == 0) { fprintf(stderr, "Error: failed to get system directory path: %u\n", (unsigned int) GetLastError()); goto cleanup; } onnxifi_library_wildcard = malloc(sizeof(WCHAR) * (system_directory_path_length + ONNXIFI_FILENAME_WILDCARD_LENGTH + 1)); if (onnxifi_library_wildcard == NULL) { fprintf(stderr, "Error: failed to allocate %Iu bytes for ONNXIFI path\n", sizeof(WCHAR) * (system_directory_path_length + ONNXIFI_FILENAME_WILDCARD_LENGTH + 1)); goto cleanup; } if (GetSystemDirectoryW( onnxifi_library_wildcard, system_directory_path_length) == 0) { fprintf(stderr, "Error: failed to get system directory path: %u\n", (unsigned int) GetLastError()); goto cleanup; } memcpy(onnxifi_library_wildcard + system_directory_path_length, ONNXIFI_FILENAME_WILDCARD, sizeof(WCHAR) * (ONNXIFI_FILENAME_WILDCARD_LENGTH + 1)); find_file_handle = FindFirstFileW(onnxifi_library_wildcard, &find_file_data); if (find_file_handle == INVALID_HANDLE_VALUE) { const DWORD error = GetLastError(); if (error != ERROR_FILE_NOT_FOUND) { fprintf(stderr, "Error: failed to list ONNXIFI libraries %S: error %u\n", onnxifi_library_wildcard, (unsigned int) error); } goto cleanup; } for (;;) { struct onnxifi_library library; if (!onnxifi_load(ONNXIFI_LOADER_FLAG_VERSION_1_0, find_file_data.cFileName, &library)) { fprintf(stderr, "Error: failed to load library %S\n", find_file_data.cFileName); continue; } struct onnxifi_library* new_libraries = realloc(libraries, (num_libraries + 1) * sizeof(struct onnxifi_library)); if (new_libraries == NULL) { fprintf(stderr, "Error: failed to allocate space for library %S\n", find_file_data.cFileName); onnxifi_unload(&library); continue; } /* All actions for the new library succeeded, commit changes */ libraries = new_libraries; memcpy(&libraries[num_libraries], &library, sizeof(library)); num_libraries++; if (FindNextFileW(find_file_handle, &find_file_data) != FALSE) { const DWORD error = GetLastError(); if (error != ERROR_NO_MORE_FILES) { fprintf(stderr, "Error: failed to some of ONNXIFI libraries %S: error %u\n", onnxifi_library_wildcard, (unsigned int) error); } break; } } cleanup: if (find_file_handle != INVALID_HANDLE_VALUE) { FindClose(find_file_handle); } free(onnxifi_library_wildcard); return TRUE; } #else static pthread_once_t init_guard = PTHREAD_ONCE_INIT; #ifndef ONNXIFI_SEARCH_DIR #ifdef __APPLE__ #define ONNXIFI_SEARCH_DIR "/opt/onnx/lib/" #else #define ONNXIFI_SEARCH_DIR "/usr/lib/" #endif #endif /* Finds filename in a null-terminated file path */ static inline const char* find_filename(const char* filepath) { const char* filename_separator = strrchr(filepath, '/'); if (filename_separator == NULL) { return filepath; } else { return filename_separator + 1; } } static inline bool is_onnxifi_path(const char* filepath) { const char* filename = find_filename(filepath); const size_t filename_length = strlen(filename); if (filename_length < ONNXIFI_FILENAME_MIN) { /* Filename too short */ return false; } const char* filename_end = filename + filename_length; /* Expected filename structure: */ if (memcmp(filename, ONNXIFI_FILENAME_PREFIX, strlen(ONNXIFI_FILENAME_PREFIX)) != 0) { /* Prefix mismatch */ return false; } const char* suffix = filename_end - strlen(ONNXIFI_FILENAME_SUFFIX); if (memcmp(suffix, ONNXIFI_FILENAME_SUFFIX, strlen(ONNXIFI_FILENAME_SUFFIX)) != 0) { /* Suffix mismatch */ return false; } return true; } static void load_all_posix_backends(void) { DIR* directory = opendir(ONNXIFI_SEARCH_DIR); if (directory == NULL) { fprintf(stderr, "Error: failed to open directory %s: %s\n", ONNXIFI_SEARCH_DIR, strerror(errno)); return; } for (;;) { /* Required to distinguish between error and end of directory in readdir */ errno = 0; struct dirent* entry = readdir(directory); if (entry == NULL) { if (errno != 0) { fprintf(stderr, "Error: failed to read directory %s: %s\n", ONNXIFI_SEARCH_DIR, strerror(errno)); } goto cleanup; } if (!is_onnxifi_path(entry->d_name)) { continue; } struct onnxifi_library library; if (!onnxifi_load(ONNXIFI_LOADER_FLAG_VERSION_1_0, entry->d_name, &library)) { fprintf(stderr, "Error: failed to load library %s\n", entry->d_name); continue; } struct onnxifi_library* new_libraries = realloc(libraries, (num_libraries + 1) * sizeof(struct onnxifi_library)); if (new_libraries == NULL) { fprintf(stderr, "Error: failed to allocate space for library %s\n", entry->d_name); onnxifi_unload(&library); continue; } /* All actions for the new library succeeded, commit changes */ libraries = new_libraries; memcpy(&libraries[num_libraries], &library, sizeof(library)); num_libraries++; } cleanup: if (directory != NULL) { if (closedir(directory) != 0) { /* Error */ fprintf(stderr, "Warning: failed to close directory %s: %s\n", ONNXIFI_SEARCH_DIR, strerror(errno)); } } } #endif /* Platform-independent wrapper for init_once */ static void load_all_libraries_once(void) { #ifdef _WIN32 InitOnceExecuteOnce(&init_guard, load_all_windows_backends, NULL, NULL); #else pthread_once(&init_guard, load_all_posix_backends); #endif } static onnxStatus wrap_backend_ids( struct onnxifi_library* library, size_t num_backends, onnxBackendID* backend_ids) { size_t num_wrapped_backends = 0; for (; num_wrapped_backends < num_backends; num_wrapped_backends++) { struct onnxifi_backend_id_wrapper* backend_id_wrapper = (struct onnxifi_backend_id_wrapper*) malloc(sizeof(struct onnxifi_backend_id_wrapper)); if (backend_id_wrapper == NULL) { goto cleanup; } backend_id_wrapper->magic = ONNXIFI_BACKEND_ID_MAGIC; backend_id_wrapper->backend_id = backend_ids[num_wrapped_backends]; backend_id_wrapper->library = library; /* Replace backend ID with its wrapper */ backend_ids[num_wrapped_backends] = (onnxBackendID) backend_id_wrapper; } return ONNXIFI_STATUS_SUCCESS; cleanup: /* Unwrap all the backends */ for (size_t i = 0; i < num_wrapped_backends; i++) { struct onnxifi_backend_id_wrapper* backend_id_wrapper = (struct onnxifi_backend_id_wrapper*) backend_ids[i]; assert(backend_id_wrapper->magic == ONNXIFI_BACKEND_ID_MAGIC); /* Replace wrapper with the wrapped backend ID */ backend_ids[i] = backend_id_wrapper->backend_id; /* Safety precaution to avoid use-after-free bugs */ memset(backend_id_wrapper, 0, sizeof(struct onnxifi_backend_id_wrapper)); free(backend_id_wrapper); } return ONNXIFI_STATUS_NO_SYSTEM_MEMORY; } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxGetBackendIDs( onnxBackendID* backendIDs, size_t* numBackends) { load_all_libraries_once(); onnxStatus status; /* Number of backend IDs requested to be stored by the caller */ const size_t num_expected_ids = (backendIDs == NULL) ? 0 : *numBackends; /* Number of backend IDs in the system */ size_t num_available_ids = 0; /* Number of backend IDs wrapped and ready to return */ size_t num_wrapped_ids = 0; onnxBackendID* backend_ids = NULL; if (num_expected_ids != 0) { backend_ids = malloc(num_expected_ids * sizeof(onnxBackendID)); if (backend_ids == NULL) { status = ONNXIFI_STATUS_NO_SYSTEM_MEMORY; goto error; } } /* Safety precaution to avoid dangling pointer bugs */ memset(backend_ids, 0, num_expected_ids * sizeof(onnxBackendID)); for (size_t l = 0; l < num_libraries; l++) { if (num_expected_ids > num_available_ids) { /* Query and wrap backend IDs from ONNXIFI library */ const size_t max_library_ids = num_expected_ids - num_available_ids; size_t num_library_ids = max_library_ids; status = libraries[l].onnxGetBackendIDs( &backend_ids[num_available_ids], &num_library_ids); const size_t num_stored_ids = (num_library_ids < max_library_ids) ? num_library_ids : max_library_ids; switch (status) { case ONNXIFI_STATUS_SUCCESS: case ONNXIFI_STATUS_FALLBACK: status = wrap_backend_ids( &libraries[l], num_stored_ids, &backend_ids[num_available_ids]); if (status != ONNXIFI_STATUS_SUCCESS) { /* Release unwrapped backends for this library */ for (size_t i = 0; i < num_stored_ids; i++) { (void) libraries[l].onnxReleaseBackendID(backend_ids[i]); /* Safety precaution to avoid use-after-free bugs */ backend_ids[i] = NULL; } /* Release wrapped backends for other libraries */ goto error; } num_wrapped_ids += num_stored_ids; num_available_ids += num_library_ids; break; default: /* Release wrapped backends for other libraries */ goto error; } } else { /* Not enough space in user-provided buffer: only count the backend IDs */ size_t num_library_ids = 0; status = libraries[l].onnxGetBackendIDs(NULL, &num_library_ids); if (status != ONNXIFI_STATUS_FALLBACK) { /* Release wrapped backends for other libraries */ goto error; } num_available_ids += num_library_ids; } } /* * Successful return: * - Copy backend IDs to user-provided memory (if applicable) * - Store number of backend IDs to user-provided variable * - Return success or fallback codes */ if (backendIDs == NULL) { *numBackends = num_available_ids; return ONNXIFI_STATUS_FALLBACK; } else { memcpy(backendIDs, backend_ids, num_wrapped_ids * sizeof(onnxBackendID)); free(backend_ids); *numBackends = num_available_ids; return num_available_ids <= num_expected_ids ? ONNXIFI_STATUS_SUCCESS : ONNXIFI_STATUS_FALLBACK; } error: /* * Error return: * - Release all wrapped backend IDs. * - Deallocate wrapper structures. */ for (size_t i = 0; i < num_wrapped_ids; i++) { struct onnxifi_backend_id_wrapper* backend_id_wrapper = (struct onnxifi_backend_id_wrapper*) backend_ids[i]; assert(backend_id_wrapper->magic == ONNXIFI_BACKEND_ID_MAGIC); (void) backend_id_wrapper->library->onnxReleaseBackendID( backend_id_wrapper->backend_id); /* Safety precaution to avoid use-after-free bugs */ memset(backend_id_wrapper, 0, sizeof(struct onnxifi_backend_id_wrapper)); free(backend_id_wrapper); } free(backend_ids); return status; } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseBackendID( onnxBackendID backendID) { if (backendID == NULL) { return ONNXIFI_STATUS_INVALID_ID; } struct onnxifi_backend_id_wrapper* backend_id_wrapper = (struct onnxifi_backend_id_wrapper*) backendID; if (backend_id_wrapper->magic != ONNXIFI_BACKEND_ID_MAGIC) { return ONNXIFI_STATUS_INVALID_ID; } if (backend_id_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_ID; } /* Call onnxReleaseBackendID with unwrapped backend ID */ const onnxStatus status = backend_id_wrapper->library->onnxReleaseBackendID( backend_id_wrapper->backend_id); /* * Note: ReleaseBackendID either succeeded, or failed with internal error. * Either way, it is not safe to use the backend ID again. */ memset(backend_id_wrapper, 0, sizeof(struct onnxifi_backend_id_wrapper)); free(backend_id_wrapper); return status; } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxGetBackendInfo( onnxBackendID backendID, onnxBackendInfo infoType, void* infoValue, size_t* infoValueSize) { if (backendID == NULL) { return ONNXIFI_STATUS_INVALID_ID; } const struct onnxifi_backend_id_wrapper* backend_id_wrapper = (const struct onnxifi_backend_id_wrapper*) backendID; if (backend_id_wrapper->magic != ONNXIFI_BACKEND_ID_MAGIC) { return ONNXIFI_STATUS_INVALID_ID; } if (backend_id_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_ID; } /* Call onnxGetBackendInfo with unwrapped backend ID */ return backend_id_wrapper->library->onnxGetBackendInfo( backend_id_wrapper->backend_id, infoType, infoValue, infoValueSize); } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxGetBackendCompatibility( onnxBackendID backendID, size_t onnxModelSize, const void* onnxModel) { if (backendID == NULL) { return ONNXIFI_STATUS_INVALID_ID; } const struct onnxifi_backend_id_wrapper* backend_id_wrapper = (const struct onnxifi_backend_id_wrapper*) backendID; if (backend_id_wrapper->magic != ONNXIFI_BACKEND_ID_MAGIC) { return ONNXIFI_STATUS_INVALID_ID; } if (backend_id_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_ID; } /* Call onnxGetBackendCompatibility with unwrapped backend ID */ return backend_id_wrapper->library->onnxGetBackendCompatibility( backend_id_wrapper->backend_id, onnxModelSize, onnxModel); } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxInitBackend( onnxBackendID backendID, const uint64_t* auxPropertiesList, onnxBackend* backend) { if (backend == NULL) { return ONNXIFI_STATUS_INVALID_POINTER; } *backend = NULL; if (backendID == NULL) { return ONNXIFI_STATUS_INVALID_ID; } const struct onnxifi_backend_id_wrapper* backend_id_wrapper = (const struct onnxifi_backend_id_wrapper*) backendID; if (backend_id_wrapper->magic != ONNXIFI_BACKEND_ID_MAGIC) { return ONNXIFI_STATUS_INVALID_ID; } if (backend_id_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_ID; } struct onnxifi_backend_wrapper* backend_wrapper = (struct onnxifi_backend_wrapper*) malloc(sizeof(struct onnxifi_backend_wrapper)); if (backend_wrapper == NULL) { return ONNXIFI_STATUS_NO_SYSTEM_MEMORY; } memset(backend_wrapper, 0, sizeof(struct onnxifi_backend_wrapper)); /* Call onnxInitBackend with unwrapped backend ID */ const onnxStatus status = backend_id_wrapper->library->onnxInitBackend( backend_id_wrapper->backend_id, auxPropertiesList, &backend_wrapper->backend); if (status == ONNXIFI_STATUS_SUCCESS) { /* Success, return wrapped graph */ backend_wrapper->magic = ONNXIFI_BACKEND_MAGIC; backend_wrapper->library = backend_id_wrapper->library; *backend = (onnxBackend) backend_wrapper; return ONNXIFI_STATUS_SUCCESS; } else { /* Failure, release allocated memory */ free(backend_wrapper); return status; } } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseBackend( onnxBackend backend) { if (backend == NULL) { return ONNXIFI_STATUS_INVALID_BACKEND; } struct onnxifi_backend_wrapper* backend_wrapper = (struct onnxifi_backend_wrapper*) backend; if (backend_wrapper->magic != ONNXIFI_BACKEND_MAGIC) { return ONNXIFI_STATUS_INVALID_BACKEND; } if (backend_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_BACKEND; } /* Call onnxReleaseBackend with unwrapped backend handle */ const onnxStatus status = backend_wrapper->library->onnxReleaseBackend( backend_wrapper->backend); /* * Note: ReleaseBackend either succeeded, or failed with internal error. * Either way, it is not safe to use the backend handle again. */ memset(backend_wrapper, 0, sizeof(struct onnxifi_backend_wrapper)); free(backend_wrapper); return status; } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxInitEvent( onnxBackend backend, onnxEvent* event) { if (event == NULL) { return ONNXIFI_STATUS_INVALID_POINTER; } *event = NULL; if (backend == NULL) { return ONNXIFI_STATUS_INVALID_BACKEND; } const struct onnxifi_backend_wrapper* backend_wrapper = (const struct onnxifi_backend_wrapper*) backend; if (backend_wrapper->magic != ONNXIFI_BACKEND_MAGIC) { return ONNXIFI_STATUS_INVALID_BACKEND; } if (backend_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_BACKEND; } struct onnxifi_event_wrapper* event_wrapper = (struct onnxifi_event_wrapper*) malloc(sizeof(struct onnxifi_event_wrapper)); if (event_wrapper == NULL) { return ONNXIFI_STATUS_NO_SYSTEM_MEMORY; } memset(event_wrapper, 0, sizeof(struct onnxifi_event_wrapper)); /* Call onnxInitEvent with unwrapped backend handle */ const onnxStatus status = backend_wrapper->library->onnxInitEvent( backend_wrapper->backend, &event_wrapper->event); if (status == ONNXIFI_STATUS_SUCCESS) { /* Success, return wrapped graph */ event_wrapper->magic = ONNXIFI_EVENT_MAGIC; event_wrapper->library = backend_wrapper->library; *event = (onnxEvent) event_wrapper; return ONNXIFI_STATUS_SUCCESS; } else { /* Failure, release allocated memory */ free(event_wrapper); return status; } } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxSignalEvent( onnxEvent event) { if (event == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } const struct onnxifi_event_wrapper* event_wrapper = (const struct onnxifi_event_wrapper*) event; if (event_wrapper->magic != ONNXIFI_EVENT_MAGIC) { return ONNXIFI_STATUS_INVALID_EVENT; } if (event_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } /* Call onnxSignalEvent with unwrapped backend handle */ return event_wrapper->library->onnxSignalEvent( event_wrapper->event); } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxGetEventState( onnxEvent event, onnxEventState* state) { if (state == NULL) { return ONNXIFI_STATUS_INVALID_POINTER; } *state = ONNXIFI_EVENT_STATE_INVALID; if (event == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } const struct onnxifi_event_wrapper* event_wrapper = (const struct onnxifi_event_wrapper*) event; if (event_wrapper->magic != ONNXIFI_EVENT_MAGIC) { return ONNXIFI_STATUS_INVALID_EVENT; } if (event_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } /* Call onnxGetEventState with unwrapped backend handle */ return event_wrapper->library->onnxGetEventState( event_wrapper->event, state); } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxWaitEvent( onnxEvent event) { if (event == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } const struct onnxifi_event_wrapper* event_wrapper = (const struct onnxifi_event_wrapper*) event; if (event_wrapper->magic != ONNXIFI_EVENT_MAGIC) { return ONNXIFI_STATUS_INVALID_EVENT; } if (event_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } /* Call onnxWaitEvent with unwrapped backend handle */ return event_wrapper->library->onnxWaitEvent( event_wrapper->event); } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseEvent( onnxEvent event) { if (event == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } struct onnxifi_event_wrapper* event_wrapper = (struct onnxifi_event_wrapper*) event; if (event_wrapper->magic != ONNXIFI_EVENT_MAGIC) { return ONNXIFI_STATUS_INVALID_EVENT; } if (event_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } /* Call onnxReleaseEvent with unwrapped event handle */ const onnxStatus status = event_wrapper->library->onnxReleaseEvent( event_wrapper->event); /* * Note: ReleaseEvent either succeeded, or failed with internal error. * Either way, it is not safe to use the event handle again. */ memset(event_wrapper, 0, sizeof(struct onnxifi_event_wrapper)); free(event_wrapper); return status; } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxInitGraph( onnxBackend backend, const uint64_t* auxPropertiesList, size_t onnxModelSize, const void* onnxModel, uint32_t weightsCount, const onnxTensorDescriptorV1* weightDescriptors, onnxGraph* graph) { if (graph == NULL) { return ONNXIFI_STATUS_INVALID_POINTER; } *graph = NULL; if (backend == NULL) { return ONNXIFI_STATUS_INVALID_BACKEND; } struct onnxifi_backend_wrapper* backend_wrapper = (struct onnxifi_backend_wrapper*) backend; if (backend_wrapper->magic != ONNXIFI_BACKEND_MAGIC) { return ONNXIFI_STATUS_INVALID_BACKEND; } if (backend_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_BACKEND; } struct onnxifi_graph_wrapper* graph_wrapper = (struct onnxifi_graph_wrapper*) malloc(sizeof(struct onnxifi_graph_wrapper)); if (graph_wrapper == NULL) { return ONNXIFI_STATUS_NO_SYSTEM_MEMORY; } memset(graph_wrapper, 0, sizeof(struct onnxifi_graph_wrapper)); /* Call onnxInitGraph with unwrapped backend handle */ const onnxStatus status = backend_wrapper->library->onnxInitGraph( backend_wrapper->backend, auxPropertiesList, onnxModelSize, onnxModel, weightsCount, weightDescriptors, &graph_wrapper->graph); switch (status) { case ONNXIFI_STATUS_SUCCESS: case ONNXIFI_STATUS_FALLBACK: /* Success, return wrapped graph */ graph_wrapper->magic = ONNXIFI_GRAPH_MAGIC; graph_wrapper->library = backend_wrapper->library; *graph = (onnxGraph) graph_wrapper; return status; default: /* Failure, release allocated memory */ free(graph_wrapper); return status; } } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxSetGraphIO( onnxGraph graph, uint32_t inputsCount, const onnxTensorDescriptorV1* inputDescriptors, uint32_t outputsCount, const onnxTensorDescriptorV1* outputDescriptors) { if (graph == NULL) { return ONNXIFI_STATUS_INVALID_GRAPH; } struct onnxifi_graph_wrapper* graph_wrapper = (struct onnxifi_graph_wrapper*) graph; if (graph_wrapper->magic != ONNXIFI_GRAPH_MAGIC) { return ONNXIFI_STATUS_INVALID_GRAPH; } if (graph_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_GRAPH; } /* Call onnxSetGraphIO with unwrapped graph handle */ return graph_wrapper->library->onnxSetGraphIO( graph_wrapper->graph, inputsCount, inputDescriptors, outputsCount, outputDescriptors); } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxRunGraph( onnxGraph graph, const onnxMemoryFenceV1* inputFence, onnxMemoryFenceV1* outputFence) { if (graph == NULL) { return ONNXIFI_STATUS_INVALID_GRAPH; } if (inputFence == NULL) { return ONNXIFI_STATUS_INVALID_POINTER; } if (outputFence == NULL) { return ONNXIFI_STATUS_INVALID_POINTER; } switch (inputFence->tag) { case ONNXIFI_TAG_MEMORY_FENCE_V1: break; default: return ONNXIFI_STATUS_UNSUPPORTED_TAG; } switch (outputFence->tag) { case ONNXIFI_TAG_MEMORY_FENCE_V1: break; default: return ONNXIFI_STATUS_UNSUPPORTED_TAG; } struct onnxifi_graph_wrapper* graph_wrapper = (struct onnxifi_graph_wrapper*) graph; if (graph_wrapper->magic != ONNXIFI_GRAPH_MAGIC) { return ONNXIFI_STATUS_INVALID_GRAPH; } if (graph_wrapper->library == NULL) { return ONNXIFI_STATUS_INVALID_GRAPH; } const onnxMemoryFenceV1* input_fence_ptr = inputFence; onnxMemoryFenceV1 input_fence_wrapper; switch (inputFence->type) { case ONNXIFI_SYNCHRONIZATION_EVENT: { if (inputFence->event == NULL) { return ONNXIFI_STATUS_INVALID_EVENT; } const struct onnxifi_event_wrapper* event_wrapper = (const struct onnxifi_event_wrapper*) inputFence->event; if (event_wrapper->magic != ONNXIFI_EVENT_MAGIC) { return ONNXIFI_STATUS_INVALID_EVENT; } if (event_wrapper->library != graph_wrapper->library) { return ONNXIFI_STATUS_INVALID_EVENT; } /* Initialize wrapper for input fence */ input_fence_wrapper.tag = ONNXIFI_TAG_MEMORY_FENCE_V1; input_fence_wrapper.type = ONNXIFI_SYNCHRONIZATION_EVENT; input_fence_wrapper.event = event_wrapper->event; input_fence_ptr = &input_fence_wrapper; break; } default: /* Pass inputFence as is */ break; } onnxMemoryFenceV1* output_fence_ptr = outputFence; onnxMemoryFenceV1 output_fence_wrapper; struct onnxifi_event_wrapper* output_event = NULL; switch (outputFence->type) { case ONNXIFI_SYNCHRONIZATION_EVENT: { /* Initialize wrapper for output fence */ output_fence_wrapper.tag = ONNXIFI_TAG_MEMORY_FENCE_V1; output_fence_wrapper.type = ONNXIFI_SYNCHRONIZATION_EVENT; /* event will be populated by onnxRunGraph */ output_fence_wrapper.event = NULL; output_fence_ptr = &output_fence_wrapper; /* * Pre-allocate memory for output event wrapper. * This must be done before onnxRunGraph, so in case allocation fails, * the function call has no side-effects. */ output_event = malloc(sizeof(struct onnxifi_event_wrapper)); if (output_event == NULL) { return ONNXIFI_STATUS_NO_SYSTEM_MEMORY; } memset(output_event, 0, sizeof(struct onnxifi_event_wrapper)); } } const onnxStatus status = graph_wrapper->library->onnxRunGraph( graph_wrapper->graph, input_fence_ptr, output_fence_ptr); if (status == ONNXIFI_STATUS_SUCCESS) { /* Wrap output event, if needed */ if (output_event != NULL) { output_event->magic = ONNXIFI_EVENT_MAGIC; output_event->event = output_fence_wrapper.event; output_event->library = graph_wrapper->library; outputFence->event = (onnxEvent) output_event; } } else { /* Deallocate output event wrapper, if needed */ free(output_event); } return status; } ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseGraph( onnxGraph graph) { if (graph == NULL) { return ONNXIFI_STATUS_INVALID_GRAPH; } struct onnxifi_graph_wrapper* graph_wrapper = (struct onnxifi_graph_wrapper*) graph; if (graph_wrapper->magic != ONNXIFI_GRAPH_MAGIC) { return ONNXIFI_STATUS_INVALID_GRAPH; } const onnxStatus status = graph_wrapper->library->onnxReleaseGraph(graph_wrapper->graph); /* * Note: ReleaseGraph either succeeded, or failed with internal error. * Either way, it is not safe to use the graph handle again. */ memset(graph_wrapper, 0, sizeof(struct onnxifi_graph_wrapper)); free(graph_wrapper); return status; } onnx-1.7.0/onnx/frontend/0000775000000000000000000000000013655345213014011 5ustar rootrootonnx-1.7.0/onnx/frontend/__init__.py0000664000000000000000000000000013655345213016110 0ustar rootrootonnx-1.7.0/onnx/onnxifi_utils.h0000664000000000000000000000057413655345213015243 0ustar rootroot#include #include #include "onnx/onnx_pb.h" #include "onnx/onnxifi.h" #include "onnx/proto_utils.h" namespace ONNX_NAMESPACE { namespace testing { onnxTensorDescriptorV1 ProtoToOnnxTensorDescriptor( const ONNX_NAMESPACE::TensorProto& proto_tensor, std::vector>& shape_pool); } // namespace testing } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/onnxifi_ext.h0000664000000000000000000003220713655345213014701 0ustar rootroot#ifndef ONNXIFI_EXT_H #define ONNXIFI_EXT_H 1 #include "onnx/onnxifi.h" #ifdef __cplusplus extern "C" { #endif /** * Generic ONNXIFI extension function pointer. * * The caller should convert this generic function pointer to the function * pointer specific for an extension function type. */ typedef onnxStatus (ONNXIFI_ABI* onnxExtensionFunctionPointer)(void); /* Function pointer declarations for dynamic loading */ typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxGetExtensionFunctionAddressFunction)( onnxBackendID backendID, const char* name, onnxExtensionFunctionPointer* function); /** * Query function pointer for an ONNXIFI extension function. * * The returned function pointer is specific to the provided backend ID, and * MUST NOT be used with objects created for other backend IDs. * * This function is a part of onnx_extension_function extension. Backends which * implement this function MUST list "onnx_extension_function" in the result of * onnxGetBackendInfo with ONNXIFI_BACKEND_EXTENSIONS information type. * * @param backendID - ID of the backend to query for extension function. * @param[in] name - name of the extension function to query. * @param[out] function - pointer to a generic function pointer for an ONNXIFI * extension function. If the function fails, the * function pointer is initialized to NULL. The caller * MUST cast this function pointer to the type specific * for the extension function before use. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the extension * function pointer is stored in the location * specified by function argument. * @retval ONNXIFI_STATUS_INVALID_ID The function call failed because backendID * is not an ONNXIFI backend ID. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * name or function argument is NULL. * @retval ONNXIFI_STATUS_UNIDENTIFIED_NAME The function call failed because * the backend does not implement * the function identified by the name. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxGetExtensionFunctionAddress( onnxBackendID backendID, const char* name, onnxExtensionFunctionPointer* function); /* Extension function pointer declarations for dynamic loading */ typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxSetIOAndRunGraphFunction)( onnxGraph graph, uint32_t inputsCount, const onnxTensorDescriptorV1* inputDescriptors, uint32_t outputsCount, const onnxTensorDescriptorV1* outputDescriptors, onnxMemoryFenceV1* outputFence); /** * A combination of onnxSetIO and onnxRunGraph, functionally equals to first run * onnxSetIO(graph, inputsCount, inputDescriptors, outputsCount, * outputDescriptors), then run onnxRunGraph(graph, inputFence, outputFence) * with an internal inputFence. * * As two separate functions, it is difficult to do atomic evaluation. * Therefore, we would like to unify this process and make it evaluable. * * @param graph - graph handle created by onnxInitGraph. * @param inputsCount - number of elements in the inputDescriptors array. * @param[in] inputDescriptors - descriptors of input tensors for the graph. * Elements of this array must provide a location * for each ValueInfoProto.name listed in * ModelProto.graph.input of the ONNX graph. * If inputsCount is non-zero, inputDescriptors * pointer must be non-NULL. * @param outputsCount - number of elements in the outputDescriptors array. * Must be greater than zero. * @param[in] outputDescriptors - descriptors of output tensors for the graph. * outputDescriptors pointer must be non-NULL. * Elements of this array must provide a location * for each ValueInfoProto.name listed in * ModelProto.graph.output of the ONNX graph. * @param[out] outputFence - synchronization primitive that signals when graph * outputs are ready to use by the caller. The type * of the synchronization primitive always must be * initialized by the caller. The type of the * synchronization primitive determines whether it * is initialized by the user before the call or by * the backend as a result of this call. Single-shot * synchronizatiom objects are initialized as a result * of the call. Reusable synchronization objects are * generally initialized by the user prior to the * call. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the all graph * inputs and outputs were matched to a memory * location. * @retval ONNXIFI_STATUS_INVALID_GRAPH The function call failed because * graph is not an ONNXIFI graph handle. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * outputDescriptors pointer is NULL or * inputDescriptors pointer is NULL while * inputsCount is non-zero. * @retval ONNXIFI_STATUS_INVALID_NAME The function call failed because one of * the names in tensor descriptors doesn't * match blob name in ModelProto.graph.input * or ModelProto.graph.output, or the same * name appears in more than one tensor * descriptor. * @retval ONNXIFI_STATUS_INVALID_SHAPE The function call failed because one of * the shape dimensions is 0. * @retval ONNXIFI_STATUS_INVALID_DATATYPE The function call failed because * one of the data types in * inputDescriptors or outputDescriptors * is unknown to the backend. * @retval ONNXIFI_STATUS_INVALID_MEMORY_TYPE The function call failed because * one of the memory types in * inputDescriptors or * outputDescriptors is unknown to * the backend. * @retval ONNXIFI_STATUS_INVALID_MEMORY_LOCATION The function call failed * because one of the memory * locations in inputDescriptors * or outputDescriptors is not * valid for the specified * memory type (e.g. NULL pointer * for ONNXIFI_MEMORY_TYPE_CPU). * @retval ONNXIFI_STATUS_UNSUPPORTED_TAG The function call failed because one * of the tags in inputDescriptors or * outputDescriptors is unknown to the * backend (tag does not match * ONNXIFI_TAG_TENSOR_DESCRIPTOR_V1). * @retval ONNXIFI_STATUS_UNSUPPORTED_SHAPE The function call failed because the * backend does not support the * tensor shapes in an input or output * of one of the operators. The * problematic tensor shapes could be * directly specified through * inputDescriptors or * outputDescriptors argument, * or inferred from the inputs by the * backend. This error code can be * returned when the backend supports * variable-size inputs and outputs, * and the problematic tensor shape was * provided in the ValueInfoProto as a * symbolic variable. * @retval ONNXIFI_STATUS_UNSUPPORTED_MEMORY_TYPE The function call failed * because the backend does not * support one of the memory * types in inputDescriptors or * outputDescriptors. * @retval ONNXIFI_STATUS_UNIDENTIFIED_NAME The function call failed because one * of the ValueInfoProto.name value in * ModelProto.graph.input or * ModelProto.graph.output doesn't have * a match in the inputDescriptors or * outputDescriptors. * @retval ONNXIFI_STATUS_MISMATCHING_SHAPE The function call failed because * the shapes specified through * inputDescriptors or * outputDescriptors argument are * inconsistent with the shapes * specified in the ONNX model graph. * @retval ONNXIFI_STATUS_MISMATCHING_DATATYPE The function call failed because * data types specified through * inputDescriptors or * outputDescriptors argument are * inconsistent with the data types * specified in the ONNX model * graph. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed because the * backend could not allocate enough * system memory to parse, analyze, and * initialize the tensor locations. * @retval ONNXIFI_STATUS_NO_SYSTEM_RESOURCES The function call failed due to * insufficient non-memory system * resources (e.g. file handles) to * initialize the tensor locations. * @retval ONNXIFI_STATUS_NO_DEVICE_MEMORY The function call failed due to * insufficient backend-specific memory * to initialize the tensor locations. * @retval ONNXIFI_STATUS_NO_DEVICE_RESOURCES The function call failed due to * insufficient non-memory * backend-specific resources (e.g. * command queues) to initialize the * tensor locations. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxSetIOAndRunGraph( onnxGraph graph, uint32_t inputsCount, const onnxTensorDescriptorV1* inputDescriptors, uint32_t outputsCount, const onnxTensorDescriptorV1* outputDescriptors, onnxMemoryFenceV1* outputFence); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* !defined(ONNXIFI_EXT_H) */ onnx-1.7.0/onnx/onnxifi_loader.h0000664000000000000000000000704313655345213015347 0ustar rootroot#ifndef ONNXIFI_LOADER_H #define ONNXIFI_LOADER_H 1 #include "onnx/onnxifi.h" #include "onnx/onnxifi_ext.h" #ifdef ONNXIFI_ENABLE_EXT #define ONNXIFI_LOADER_FUNCTION_COUNT 16 #else #define ONNXIFI_LOADER_FUNCTION_COUNT 15 #endif #define ONNXIFI_LOADER_FLAG_VERSION_MASK 0xFF #define ONNXIFI_LOADER_FLAG_VERSION_1_0 0x01 #ifndef ONNXIFI_HIDDEN #if defined(__ELF__) #define ONNXIFI_HIDDEN __attribute__((__visibility__("hidden"))) #elif defined(__MACH__) #define ONNXIFI_HIDDEN __attribute__((__visibility__("hidden"))) #else #define ONNXIFI_HIDDEN #endif #endif /* !defined(ONNXIFI_HIDDEN) */ struct onnxifi_library { /* * Opaque handle for the loaded ONNXIFI library. * * Note: this is the value returned from LoadLibraryW (on Windows), or * dlopen (on other operating systems and environments). */ void* handle; /* * Options used for dynamic loading of the ONNXIFI library and its API * functions. These are the options passed in flags parameter to onnxifi_load. */ uint32_t flags; union { struct { onnxGetBackendIDsFunction onnxGetBackendIDs; onnxReleaseBackendIDFunction onnxReleaseBackendID; onnxGetBackendInfoFunction onnxGetBackendInfo; onnxGetBackendCompatibilityFunction onnxGetBackendCompatibility; onnxInitBackendFunction onnxInitBackend; onnxReleaseBackendFunction onnxReleaseBackend; onnxInitEventFunction onnxInitEvent; onnxSignalEventFunction onnxSignalEvent; onnxGetEventStateFunction onnxGetEventState; onnxWaitEventFunction onnxWaitEvent; onnxReleaseEventFunction onnxReleaseEvent; onnxInitGraphFunction onnxInitGraph; onnxSetGraphIOFunction onnxSetGraphIO; onnxRunGraphFunction onnxRunGraph; onnxReleaseGraphFunction onnxReleaseGraph; #ifdef ONNXIFI_ENABLE_EXT onnxGetExtensionFunctionAddressFunction onnxGetExtensionFunctionAddress; #endif }; void* functions[ONNXIFI_LOADER_FUNCTION_COUNT]; }; }; #ifdef __cplusplus extern "C" { #endif /** * Dynamically load the ONNXIFI library. * * @param flags - options for dynamic loading of the ONNXIFI library. * The ONNXIFI_LOADER_FLAG_VERSION_MASK part of the flags * specifies which ONNX API function should be loaded. The only * currently supported value is ONNXIFI_LOADER_FLAG_VERSION_1_0. * @param path - optional path to the ONNXIFI library to load. * If this argument is null, the default path is used. * @param[out] library - the structure representing the dynamic library and * its API functions. On success, this structure will * be initialized with valid pointers to implentation. * On failure, this structure will be zero-initialized. * * @return Non-zero if the function succeeds, or zero if the function fails. */ ONNXIFI_HIDDEN int ONNXIFI_ABI onnxifi_load( uint32_t flags, #ifdef _WIN32 const wchar_t* path, #else const char* path, #endif struct onnxifi_library* library); /** * Unload the dynamically loaded ONNXIFI library. * * @param[in,out] library - the structure representing the dynamic library and * its API functions. If this structure is * zero-initialized, the function does nothing. * The function zero-initialized the structure before * returning. */ ONNXIFI_HIDDEN void ONNXIFI_ABI onnxifi_unload( struct onnxifi_library* library); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* !defined(ONNXIFI_LOADER_H) */ onnx-1.7.0/onnx/onnx.in.proto0000664000000000000000000007302113655345213014651 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. syntax = "proto2"; package {PACKAGE_NAME}; // Overview // // ONNX is an open specification that is comprised of the following components: // // 1) A definition of an extensible computation graph model. // 2) Definitions of standard data types. // 3) Definitions of built-in operators. // // This document describes the syntax of models and their computation graphs, // as well as the standard data types. Together, they are referred to as the ONNX // Intermediate Representation, or 'IR' for short. // // The normative semantic specification of the ONNX IR is found in docs/IR.md. // Definitions of the built-in neural network operators may be found in docs/Operators.md. // #if ONNX-ML // Definitions of the built-in classical machine learning operators may be found in // docs/Operators-ml.md. // #endif // Notes // // Release // // We are still in the very early stage of defining ONNX. The current // version of ONNX is a starting point. While we are actively working // towards a complete spec, we would like to get the community involved // by sharing our working version of ONNX. // // Protobuf compatibility // // To simplify framework compatibility, ONNX is defined using the subset of protobuf // that is compatible with both protobuf v2 and v3. This means that we do not use any // protobuf features that are only available in one of the two versions. // // Here are the most notable contortions we have to carry out to work around // these limitations: // // - No 'map' (added protobuf 3.0). We instead represent mappings as lists // of key-value pairs, where order does not matter and duplicates // are not allowed. // Versioning // // ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md // // To be compatible with both proto2 and proto3, we will use a version number // that is not defined by the default value but an explicit enum number. enum Version { // proto3 requires the first enum value to be zero. // We add this just to appease the compiler. _START_VERSION = 0; // The version field is always serialized and we will use it to store the // version that the graph is generated from. This helps us set up version // control. // For the IR, we are using simple numbers starting with 0x00000001, // which was the version we published on Oct 10, 2017. IR_VERSION_2017_10_10 = 0x0000000000000001; // IR_VERSION 2 published on Oct 30, 2017 // - Added type discriminator to AttributeProto to support proto3 users IR_VERSION_2017_10_30 = 0x0000000000000002; // IR VERSION 3 published on Nov 3, 2017 // - For operator versioning: // - Added new message OperatorSetIdProto // - Added opset_import in ModelProto // - For vendor extensions, added domain in NodeProto IR_VERSION_2017_11_3 = 0x0000000000000003; // IR VERSION 4 published on Jan 22, 2019 // - Relax constraint that initializers should be a subset of graph inputs // - Add type BFLOAT16 IR_VERSION_2019_1_22 = 0x0000000000000004; // IR VERSION 5 published on March 18, 2019 // - Add message TensorAnnotation. // - Add quantization annotation in GraphProto to map tensor with its scale and zero point quantization parameters. IR_VERSION_2019_3_18 = 0x0000000000000005; // IR VERSION 6 published on Sep 19, 2019 // - Add support for sparse tensor constants stored in model. // - Add message SparseTensorProto // - Add sparse initializers IR_VERSION_2019_9_19 = 0x0000000000000006; // IR VERSION 7 published on // - Add support to allow function body graph to rely on multiple external opreator sets. // - Add a list to promote inference graph's initializers to global and // mutable variables. Global variables are visible in all graphs of the // stored models. // - Add message TrainingInfoProto to store initialization // method and training algorithm. The execution of TrainingInfoProto // can modify the values of mutable variables. // - Make inference graph callable from TrainingInfoProto via GraphCall operator. IR_VERSION = 0x0000000000000007; } // Attributes // // A named attribute containing either singular float, integer, string, graph, // and tensor values, or repeated float, integer, string, graph, and tensor values. // An AttributeProto MUST contain the name field, and *only one* of the // following content fields, effectively enforcing a C/C++ union equivalent. message AttributeProto { // Note: this enum is structurally identical to the OpSchema::AttrType // enum defined in schema.h. If you rev one, you likely need to rev the other. enum AttributeType { UNDEFINED = 0; FLOAT = 1; INT = 2; STRING = 3; TENSOR = 4; GRAPH = 5; SPARSE_TENSOR = 11; FLOATS = 6; INTS = 7; STRINGS = 8; TENSORS = 9; GRAPHS = 10; SPARSE_TENSORS = 12; } // The name field MUST be present for this version of the IR. optional string name = 1; // namespace Attribute // if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function. // In this case, this AttributeProto does not contain data, and it's a reference of attribute // in parent scope. // NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph. optional string ref_attr_name = 21; // A human-readable documentation for this attribute. Markdown is allowed. optional string doc_string = 13; // The type field MUST be present for this version of the IR. // For 0.0.1 versions of the IR, this field was not defined, and // implementations needed to use has_field heuristics to determine // which value field was in use. For IR_VERSION 0.0.2 or later, this // field MUST be set and match the f|i|s|t|... field in use. This // change was made to accommodate proto3 implementations. optional AttributeType type = 20; // discriminator that indicates which field below is in use // Exactly ONE of the following fields must be present for this version of the IR optional float f = 2; // float optional int64 i = 3; // int optional bytes s = 4; // UTF-8 string optional TensorProto t = 5; // tensor value optional GraphProto g = 6; // graph optional SparseTensorProto sparse_tensor = 22; // sparse tensor value // Do not use field below, it's deprecated. // optional ValueProto v = 12; // value - subsumes everything but graph repeated float floats = 7; // list of floats repeated int64 ints = 8; // list of ints repeated bytes strings = 9; // list of UTF-8 strings repeated TensorProto tensors = 10; // list of tensors repeated GraphProto graphs = 11; // list of graph repeated SparseTensorProto sparse_tensors = 23; // list of sparse tensors } // Defines information on value, including the name, the type, and // the shape of the value. message ValueInfoProto { // This field MUST be present in this version of the IR. optional string name = 1; // namespace Value // This field MUST be present in this version of the IR for // inputs and outputs of the top-level graph. optional TypeProto type = 2; // A human-readable documentation for this value. Markdown is allowed. optional string doc_string = 3; } // Nodes // // Computation graphs are made up of a DAG of nodes, which represent what is // commonly called a "layer" or "pipeline stage" in machine learning frameworks. // // For example, it can be a node of type "Conv" that takes in an image, a filter // tensor and a bias tensor, and produces the convolved output. message NodeProto { repeated string input = 1; // namespace Value repeated string output = 2; // namespace Value // An optional identifier for this node in a graph. // This field MAY be absent in ths version of the IR. optional string name = 3; // namespace Node // The symbolic identifier of the Operator to execute. optional string op_type = 4; // namespace Operator // The domain of the OperatorSet that specifies the operator named by op_type. optional string domain = 7; // namespace Domain // Additional named attributes. repeated AttributeProto attribute = 5; // A human-readable documentation for this node. Markdown is allowed. optional string doc_string = 6; } // Training information // TrainingInfoProto stores information for training a model. // In particular, this defines two functionalities: an initialization-step // and a training-algorithm-step. Initialization resets the model // back to its original state as if no training has been consumed. // Training algorithm improves the model based on input data. // // The semantics of the initialization-step is that the initializers // in ModelProto.graph and in TrainingInfoProto.algorithm are first // initialized as specified by the initializers in the graph, and then // updated by the "initialization_binding" in every instance in // ModelProto.training_info. // // The field "algorithm" defines a computation graph which represents a // training algorithm's step. After the execution of a // TrainingInfoProto.algorithm, the initializers specified by "update_binding" // may be immediately updated. If the targeted training algorithm contains // consecutive update stages (such as block coordinate descent methods), // the user needs to create a TrainingInfoProto for each stage. message TrainingInfoProto { // This field describes a graph to compute the initial tensors // upon starting the training process. Initialization graph has no input // and can have multiple outputs. Usually, trainable tensors in neural // networks are randomly initialized. To achieve that, for each tensor, // the user can put a random number operator such as RandomNormal or // RandomUniform in TrainingInfoProto.initialization.node and assign its // random output to the specific tensor using "initialization_binding". // This graph can also set the initializers in "algorithm" in the same // TrainingInfoProto; a use case is resetting the number of training // iteration to zero. // // By default, this field is an empty graph and its evaluation does not // produce any output. optional GraphProto initialization = 1; // This field represents a training algorithm step. Given required inputs, // it computes outputs to update initializers in its own or inference graph's // initializer lists. In general, this graph contains loss node, gradient node, // optimizer node, increment of iteration count, and some calls to the inference // graph. // // The field algorithm.node is the only place the user can use GraphCall // operator. The only callable graph is the one stored in ModelProto.graph. // // By default, this field is an empty graph and its evaluation does not // produce any output. optional GraphProto algorithm = 2; // This field specifies the bindings from the outputs of "initialization" to // some initializers in "ModelProto.graph.initializer" and // the "algorithm.initializer" in the same TrainingInfoProto. // See "update_binding" below for details. // // By default, this field is empty and no initializer would be changed // by the execution of "initialization". repeated StringStringEntryProto initialization_binding = 3; // Gradient-based training is usually an iterative procedure. In one gradient // descent iteration, we apply // // x = x - r * g // // where "x" is the optimized tensor, "r" stands for learning rate, and "g" is // gradient of "x" with respect to a chosen loss. To avoid adding assignments // into the training graph, we split the update equation into // // y = x - r * g // x = y // // The user needs to save "y = x - r * g" into TrainingInfoProto.algorithm. To // tell that "y" should be assigned to "x", the field "update_binding" may // contain a key-value pair of strings, "x" (key of StringStringEntryProto) // and "y" (value of StringStringEntryProto). // For a neural network with multiple trainable (mutable) tensors, there can // be multiple key-value pairs in "update_binding". // // The initializers appears as keys in "update_binding" are considered // mutable and globally-visible variables. This implies some behaviors // as described below. // // 1. We have only unique keys in all "update_binding"s so that two global // variables may not have the same name. This ensures that one // global variable is assigned up to once. // 2. The keys must appear in names of "ModelProto.graph.initializer" or // "TrainingInfoProto.algorithm.initializer". // 3. The values must be output names of "algorithm". // 4. If an optional input of a graph is omitted when using GraphCall, the // global variable with the same name may be used. // 5. When using GraphCall, the users always can pass values to optional // inputs of the called graph even if the associated initializers appears // as keys in "update_binding"s. // 6. The graphs in TrainingInfoProto's can use global variables as // their operator inputs. // 7. Mutable variables are initialized to the value specified by the // corresponding initializer, and then potentially updated by // "initializer_binding"s and "update_binding"s in "TrainingInfoProto"s. // // This field usually contains names of trainable tensors // (in ModelProto.graph), optimizer states such as momentums in advanced // stochastic gradient methods (in TrainingInfoProto.graph), // and number of training iterations (in TrainingInfoProto.graph). // // By default, this field is empty and no initializer would be changed // by the execution of "algorithm". repeated StringStringEntryProto update_binding = 4; } // Models // // ModelProto is a top-level file/container format for bundling a ML model and // associating its computation graph with metadata. // // The semantics of the model are described by the associated GraphProto's. message ModelProto { // The version of the IR this model targets. See Version enum above. // This field MUST be present. optional int64 ir_version = 1; // The OperatorSets this model relies on. // All ModelProtos MUST have at least one entry that // specifies which version of the ONNX OperatorSet is // being imported. // // All nodes in the ModelProto's graph will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. repeated OperatorSetIdProto opset_import = 8; // The name of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. optional string producer_name = 2; // The version of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. optional string producer_version = 3; // Domain name of the model. // We use reverse domain names as name space indicators. For example: // `com.facebook.fair` or `com.microsoft.cognitiveservices` // // Together with `model_version` and GraphProto.name, this forms the unique identity of // the graph. optional string domain = 4; // The version of the graph encoded. See Version enum below. optional int64 model_version = 5; // A human-readable documentation for this model. Markdown is allowed. optional string doc_string = 6; // The parameterized graph that is evaluated to execute the model. optional GraphProto graph = 7; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 14; // Training-specific information. Sequentially executing all stored // `TrainingInfoProto.algorithm`s and assigning their outputs following // the corresponding `TrainingInfoProto.update_binding`s is one training // iteration. Similarly, to initialize the model // (as if training hasn't happened), the user should sequentially execute // all stored `TrainingInfoProto.initialization`s and assigns their outputs // using `TrainingInfoProto.initialization_binding`s. // // If this field is empty, the training behavior of the model is undefined. repeated TrainingInfoProto training_info = 20; }; // StringStringEntryProto follows the pattern for cross-proto-version maps. // See https://developers.google.com/protocol-buffers/docs/proto3#maps message StringStringEntryProto { optional string key = 1; optional string value= 2; }; message TensorAnnotation { optional string tensor_name = 1; // pairs to annotate tensor specified by above. // The keys used in the mapping below must be pre-defined in ONNX spec. // For example, for 8-bit linear quantization case, 'SCALE_TENSOR', 'ZERO_POINT_TENSOR' will be pre-defined as // quantization parameter keys. repeated StringStringEntryProto quant_parameter_tensor_names = 2; } // Graphs // // A graph defines the computational logic of a model and is comprised of a parameterized // list of nodes that form a directed acyclic graph based on their inputs and outputs. // This is the equivalent of the "network" or "graph" in many deep learning // frameworks. message GraphProto { // The nodes in the graph, sorted topologically. repeated NodeProto node = 1; // The name of the graph. optional string name = 2; // namespace Graph // A list of named tensor values, used to specify constant inputs of the graph. // Each TensorProto entry must have a distinct name (within the list) that // MAY also appear in the input list. repeated TensorProto initializer = 5; // Initializers (see above) stored in sparse format. repeated SparseTensorProto sparse_initializer = 15; // A human-readable documentation for this graph. Markdown is allowed. optional string doc_string = 10; // The inputs and outputs of the graph. repeated ValueInfoProto input = 11; repeated ValueInfoProto output = 12; // Information for the values in the graph. The ValueInfoProto.name's // must be distinct. It is optional for a value to appear in value_info list. repeated ValueInfoProto value_info = 13; // This field carries information to indicate the mapping among a tensor and its // quantization parameter tensors. For example: // For tensor 'a', it may have {'SCALE_TENSOR', 'a_scale'} and {'ZERO_POINT_TENSOR', 'a_zero_point'} annotated, // which means, tensor 'a_scale' and tensor 'a_zero_point' are scale and zero point of tensor 'a' in the model. repeated TensorAnnotation quantization_annotation = 14; // DO NOT USE the following fields, they were deprecated from earlier versions. // repeated string input = 3; // repeated string output = 4; // optional int64 ir_version = 6; // optional int64 producer_version = 7; // optional string producer_tag = 8; // optional string domain = 9; } // Tensors // // A serialized tensor value. message TensorProto { enum DataType { UNDEFINED = 0; // Basic types. FLOAT = 1; // float UINT8 = 2; // uint8_t INT8 = 3; // int8_t UINT16 = 4; // uint16_t INT16 = 5; // int16_t INT32 = 6; // int32_t INT64 = 7; // int64_t STRING = 8; // string BOOL = 9; // bool // IEEE754 half-precision floating-point format (16 bits wide). // This format has 1 sign bit, 5 exponent bits, and 10 mantissa bits. FLOAT16 = 10; DOUBLE = 11; UINT32 = 12; UINT64 = 13; COMPLEX64 = 14; // complex with float32 real and imaginary components COMPLEX128 = 15; // complex with float64 real and imaginary components // Non-IEEE floating-point format based on IEEE754 single-precision // floating-point number truncated to 16 bits. // This format has 1 sign bit, 8 exponent bits, and 7 mantissa bits. BFLOAT16 = 16; // Future extensions go here. } // The shape of the tensor. repeated int64 dims = 1; // The data type of the tensor. // This field MUST have a valid TensorProto.DataType value optional int32 data_type = 2; // For very large tensors, we may want to store them in chunks, in which // case the following fields will specify the segment that is stored in // the current TensorProto. message Segment { optional int64 begin = 1; optional int64 end = 2; } optional Segment segment = 3; // Tensor content must be organized in row-major order. // // Depending on the data_type field, exactly one of the fields below with // name ending in _data is used to store the elements of the tensor. // For float and complex64 values // Complex64 tensors are encoded as a single array of floats, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be FLOAT or COMPLEX64. repeated float float_data = 4 [packed = true]; // For int32, uint8, int8, uint16, int16, bool, and float16 values // float16 values must be bit-wise converted to an uint16_t prior // to writing to the buffer. // When this field is present, the data_type field MUST be // INT32, INT16, INT8, UINT16, UINT8, BOOL, or FLOAT16 repeated int32 int32_data = 5 [packed = true]; // For strings. // Each element of string_data is a UTF-8 encoded Unicode // string. No trailing null, no leading BOM. The protobuf "string" // scalar type is not used to match ML community conventions. // When this field is present, the data_type field MUST be STRING repeated bytes string_data = 6; // For int64. // When this field is present, the data_type field MUST be INT64 repeated int64 int64_data = 7 [packed = true]; // Optionally, a name for the tensor. optional string name = 8; // namespace Value // A human-readable documentation for this tensor. Markdown is allowed. optional string doc_string = 12; // Serializations can either use one of the fields above, or use this // raw bytes field. The only exception is the string case, where one is // required to store the content in the repeated bytes string_data field. // // When this raw_data field is used to store tensor value, elements MUST // be stored in as fixed-width, little-endian order. // Floating-point data types MUST be stored in IEEE 754 format. // Complex64 elements must be written as two consecutive FLOAT values, real component first. // Complex128 elements must be written as two consecutive DOUBLE values, real component first. // Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false). // // Note: the advantage of specific field rather than the raw_data field is // that in some cases (e.g. int data), protobuf does a better packing via // variable length storage, and may lead to smaller binary footprint. // When this field is present, the data_type field MUST NOT be STRING or UNDEFINED optional bytes raw_data = 9; // Data can be stored inside the protobuf file using type-specific fields or raw_data. // Alternatively, raw bytes data can be stored in an external file, using the external_data field. // external_data stores key-value pairs describing data location. Recognized keys are: // - "location" (required) - POSIX filesystem path relative to the directory where the ONNX // protobuf model was stored // - "offset" (optional) - position of byte at which stored data begins. Integer stored as string. // Offset values SHOULD be multiples 4096 (page size) to enable mmap support. // - "length" (optional) - number of bytes containing data. Integer stored as string. // - "checksum" (optional) - SHA1 digest of file specified in under 'location' key. repeated StringStringEntryProto external_data = 13; // Location of the data for this tensor. MUST be one of: // - DEFAULT - data stored inside the protobuf message. Data is stored in raw_data (if set) otherwise in type-specified field. // - EXTERNAL - data stored in an external location as described by external_data field. enum DataLocation { DEFAULT = 0; EXTERNAL = 1; } // If value not set, data is stored in raw_data (if set) otherwise in type-specified field. optional DataLocation data_location = 14; // For double // Complex128 tensors are encoded as a single array of doubles, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be DOUBLE or COMPLEX128 repeated double double_data = 10 [packed = true]; // For uint64 and uint32 values // When this field is present, the data_type field MUST be // UINT32 or UINT64 repeated uint64 uint64_data = 11 [packed = true]; } // A serialized sparse-tensor value message SparseTensorProto { // The sequence of non-default values are encoded as a tensor of shape [NNZ]. // The default-value is zero for numeric tensors, and empty-string for string tensors. optional TensorProto values = 1; // The indices of the non-default values, which may be stored in one of two formats. // (a) Indices can be a tensor of shape [NNZ, rank] with the [i,j]-th value // corresponding to the j-th index of the i-th value (in the values tensor). // (b) Indices can be a tensor of shape [NNZ], in which case the i-th value // must be the linearized-index of the i-th value (in the values tensor). // The linearized-index can be converted into an index tuple (k_1,...,k_rank) // using the shape provided below. // The indices must appear in ascending order without duplication. // In the first format, the ordering is lexicographic-ordering: // e.g., index-value [1,4] must appear before [2,1] optional TensorProto indices = 2; // The shape of the underlying dense-tensor: [dim_1, dim_2, ... dim_rank] repeated int64 dims = 3; } // Defines a tensor shape. A dimension can be either an integer value // or a symbolic variable. A symbolic variable represents an unknown // dimension. message TensorShapeProto { message Dimension { oneof value { int64 dim_value = 1; string dim_param = 2; // namespace Shape }; // Standard denotation can optionally be used to denote tensor // dimensions with standard semantic descriptions to ensure // that operations are applied to the correct axis of a tensor. // Refer to https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definition // for pre-defined dimension denotations. optional string denotation = 3; }; repeated Dimension dim = 1; } // Types // // The standard ONNX data types. message TypeProto { message Tensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. optional int32 elem_type = 1; optional TensorShapeProto shape = 2; } // repeated T message Sequence { // The type and optional shape of each element of the sequence. // This field MUST be present for this version of the IR. optional TypeProto elem_type = 1; }; // map message Map { // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. // This field MUST refer to an integral type ([U]INT{8|16|32|64}) or STRING optional int32 key_type = 1; // This field MUST be present for this version of the IR. optional TypeProto value_type = 2; }; // #if ONNX-ML message SparseTensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. optional int32 elem_type = 1; optional TensorShapeProto shape = 2; } message Opaque { // When missing, the domain is the same as the model's. optional string domain = 1; // The name is optional but significant when provided. optional string name = 2; // parameters that help defining the type // DEPRECATED do not use. // repeated TypeProto parameters = 3; } // #endif oneof value { // The type of a tensor. Tensor tensor_type = 1; // NOTE: DNN-only implementations of ONNX MAY elect to not support non-tensor values // as input and output to graphs and nodes. These types are needed to naturally // support classical ML operators. DNN operators SHOULD restrict their input // and output types to tensors. // The type of a sequence. Sequence sequence_type = 4; // The type of a map. Map map_type = 5; // #if ONNX-ML SparseTensor sparse_tensor_type = 8; Opaque opaque_type = 7; // #endif } // An optional denotation can be used to denote the whole // type with a standard semantic description as to what is // stored inside. Refer to https://github.com/onnx/onnx/blob/master/docs/TypeDenotation.md#type-denotation-definition // for pre-defined type denotations. optional string denotation = 6; } // Operator Sets // // OperatorSets are uniquely identified by a (domain, opset_version) pair. message OperatorSetIdProto { // The domain of the operator set being identified. // The empty string ("") or absence of this field implies the operator // set that is defined as part of the ONNX specification. // This field MUST be present in this version of the IR when referring to any other operator set. optional string domain = 1; // The version of the operator set being identified. // This field MUST be present in this version of the IR. optional int64 version = 2; } onnx-1.7.0/onnx/onnx-operators-ml.proto0000664000000000000000000001626413655345213016674 0ustar rootroot// // WARNING: This file is automatically generated! Please edit onnx.in.proto. // // Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. syntax = "proto2"; package onnx; import "onnx/onnx-ml.proto"; // // This file contains the proto definitions for OperatorSetProto and // OperatorProto. OperatorSetProtos are used to describe a versioned // set of operators that can be used by a ModelProto. // // Like ModelProto, OperatorSetProto is defined as a top-level file/wire // format, however their usage is different. // // ModelProto files are used to describe executable graphs that can be // executed directly by a framework, runtime, or engine. // // OperatorSetProto files are used to describe a set of operators that are // available in a given environment. The file TBD.TBD is the OperatorSetProto // that describes the ONNX standard operators. // // Operator/function status. enum OperatorStatus { EXPERIMENTAL = 0; STABLE = 1; } message FunctionProto { // The name of the function, similar usage of op_type in OperatorProto. optional string name = 1; // The first version of a function set which contains this function. // When there's any breaking change for this function, the function set // contains the function needs to bump its version, and since_version of // the updated function will be changed to the updated function set version. optional int64 since_version = 2; // This field indicates whether the syntax, semantics, or presence // of this function is in an experimental or stable stage. Once an // function is published as STABLE, its syntax and semantics MUST NOT // change in subsequent versions of the operator set. // When a function is published as EXPERIMENTAL, the syntax and semantics // of the function MAY change across operator set versions. // Functions "become" stable by deprecating the experimental version and // introducing a new stable function with the same name. optional OperatorStatus status = 3; // The inputs and outputs of the function. repeated string input = 4; repeated string output = 5; // The attributes of the function. repeated string attribute= 6; // The nodes in the function. repeated NodeProto node = 7; // A human-readable documentation for this function. Markdown is allowed. optional string doc_string = 8; // The OperatorSets this function body (graph) relies on. // A FunctionProto body (graph) may implicitly rely on the OperatorSet that // this function belongs to. It can also explicitly rely on more OperatorSets // with this field specified. // // All nodes in the function body (graph) will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. This means at most one version can be relied // for one domain. repeated OperatorSetIdProto opset_import = 9; } // An OperatorProto represents the immutable specification of the signature // and semantics of an operator. // // Operators are declared as part of an OperatorSet, which also defines the // domain name for the set. // // Operators are uniquely identified by a three part identifier // (domain, op_type, since_version) // where // *domain* is the domain of an operator set that // contains this operator specification. // // *op_type* is the name of the operator as referenced by a // NodeProto.op_type // // *since_version* is the version of the operator set that // this operator was initially declared in. // message OperatorProto { // The name of the operator within a domain. // This field MUST be present in this version of the IR. optional string op_type = 1; // The version of the operator set that first introduced this // operator. This value MUST be the same value as the // opset_version of the operator set that first published this operator. // Subsequent versions of the operator set MUST NOT alter the signature // or semantics of the operator once published as STABLE. // This field MUST be present in this version of the IR. optional int64 since_version = 2; // This field indicates whether the syntax, semantics, or presence // of this operator is in an experimental or stable stage. Once an // operator is published as STABLE, it's syntax and semantics MUST NOT // change in subsequent versions of the operator set. // When an operator is published as EXPERIMENTAL, the syntax and semantics // of the operator MAY change across operator set versions. // Operators "become" stable by deprecating the experimental version and // introducing a new stable operator with the same op_type. optional OperatorStatus status = 3; // Eventually we will declare the signature of the operator here // A human-readable documentation for this operator. Markdown is allowed. optional string doc_string = 10; } // An OperatorSetProto represents an immutable set of immutable operator // specifications. // // The domain of the set (OperatorSetProto.domain) is a reverse-DNS name // that disambiguates operator sets defined by independent entities. // // The version of the set (opset_version) is a monotonically increasing // integer that indicates changes to the membership of the operator set. // // // Operator sets are uniquely identified by a two part identifier (domain, opset_version) // // Like ModelProto, OperatorSetProto is intended as a top-level file/wire format, // and thus has the standard format headers in addition to the operator set information. // message OperatorSetProto { // All OperatorSetProtos start with a distingushed byte sequence to disambiguate // protobuf files containing OperatorSets from other content. // This field MUST be "ONNXOPSET" // This field MUST be present in this version of the IR optional string magic = 1; // All OperatorSetProtos indicate the version of the IR syntax and semantics // they adhere to. It is always IR_VERSION. // This field MUST be present in this version of the IR optional int64 ir_version = 2; // The prerelease component of the SemVer of the IR. // This field MAY be absent in this version of the IR optional string ir_version_prerelease = 3; // The build metadata component of the SemVer of the IR. // This field MAY be absent in this version of the IR optional string ir_build_metadata = 7; // Domain name of the operator set, in reverse DNS form (e.g., com.acme.dnnops). optional string domain = 4; // The version of the set of operators. This is a simple int value // that is monotonically increasing as new versions of operator set // are published. All operators in this set MUST have version // numbers no greater than opset_version. optional int64 opset_version = 5; // A human-readable documentation for this set of operators. Markdown is allowed. optional string doc_string = 6; // The operators specified by this operator set. // The (name, version) MUST be unique across all OperatorProtos in operator repeated OperatorProto operator = 8; // The functions specified by this operator set. // The (name, version) MUST be unique across all OperatorProtos/FunctionProtos in operator/functions repeated FunctionProto functions = 9; } // For using protobuf-lite option optimize_for = LITE_RUNTIME; onnx-1.7.0/onnx/onnx.proto0000664000000000000000000007147113655345213014253 0ustar rootroot// // WARNING: This file is automatically generated! Please edit onnx.in.proto. // // Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. syntax = "proto2"; package onnx; // Overview // // ONNX is an open specification that is comprised of the following components: // // 1) A definition of an extensible computation graph model. // 2) Definitions of standard data types. // 3) Definitions of built-in operators. // // This document describes the syntax of models and their computation graphs, // as well as the standard data types. Together, they are referred to as the ONNX // Intermediate Representation, or 'IR' for short. // // The normative semantic specification of the ONNX IR is found in docs/IR.md. // Definitions of the built-in neural network operators may be found in docs/Operators.md. // Notes // // Release // // We are still in the very early stage of defining ONNX. The current // version of ONNX is a starting point. While we are actively working // towards a complete spec, we would like to get the community involved // by sharing our working version of ONNX. // // Protobuf compatibility // // To simplify framework compatibility, ONNX is defined using the subset of protobuf // that is compatible with both protobuf v2 and v3. This means that we do not use any // protobuf features that are only available in one of the two versions. // // Here are the most notable contortions we have to carry out to work around // these limitations: // // - No 'map' (added protobuf 3.0). We instead represent mappings as lists // of key-value pairs, where order does not matter and duplicates // are not allowed. // Versioning // // ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md // // To be compatible with both proto2 and proto3, we will use a version number // that is not defined by the default value but an explicit enum number. enum Version { // proto3 requires the first enum value to be zero. // We add this just to appease the compiler. _START_VERSION = 0; // The version field is always serialized and we will use it to store the // version that the graph is generated from. This helps us set up version // control. // For the IR, we are using simple numbers starting with 0x00000001, // which was the version we published on Oct 10, 2017. IR_VERSION_2017_10_10 = 0x0000000000000001; // IR_VERSION 2 published on Oct 30, 2017 // - Added type discriminator to AttributeProto to support proto3 users IR_VERSION_2017_10_30 = 0x0000000000000002; // IR VERSION 3 published on Nov 3, 2017 // - For operator versioning: // - Added new message OperatorSetIdProto // - Added opset_import in ModelProto // - For vendor extensions, added domain in NodeProto IR_VERSION_2017_11_3 = 0x0000000000000003; // IR VERSION 4 published on Jan 22, 2019 // - Relax constraint that initializers should be a subset of graph inputs // - Add type BFLOAT16 IR_VERSION_2019_1_22 = 0x0000000000000004; // IR VERSION 5 published on March 18, 2019 // - Add message TensorAnnotation. // - Add quantization annotation in GraphProto to map tensor with its scale and zero point quantization parameters. IR_VERSION_2019_3_18 = 0x0000000000000005; // IR VERSION 6 published on Sep 19, 2019 // - Add support for sparse tensor constants stored in model. // - Add message SparseTensorProto // - Add sparse initializers IR_VERSION_2019_9_19 = 0x0000000000000006; // IR VERSION 7 published on // - Add support to allow function body graph to rely on multiple external opreator sets. // - Add a list to promote inference graph's initializers to global and // mutable variables. Global variables are visible in all graphs of the // stored models. // - Add message TrainingInfoProto to store initialization // method and training algorithm. The execution of TrainingInfoProto // can modify the values of mutable variables. // - Make inference graph callable from TrainingInfoProto via GraphCall operator. IR_VERSION = 0x0000000000000007; } // Attributes // // A named attribute containing either singular float, integer, string, graph, // and tensor values, or repeated float, integer, string, graph, and tensor values. // An AttributeProto MUST contain the name field, and *only one* of the // following content fields, effectively enforcing a C/C++ union equivalent. message AttributeProto { // Note: this enum is structurally identical to the OpSchema::AttrType // enum defined in schema.h. If you rev one, you likely need to rev the other. enum AttributeType { UNDEFINED = 0; FLOAT = 1; INT = 2; STRING = 3; TENSOR = 4; GRAPH = 5; SPARSE_TENSOR = 11; FLOATS = 6; INTS = 7; STRINGS = 8; TENSORS = 9; GRAPHS = 10; SPARSE_TENSORS = 12; } // The name field MUST be present for this version of the IR. optional string name = 1; // namespace Attribute // if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function. // In this case, this AttributeProto does not contain data, and it's a reference of attribute // in parent scope. // NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph. optional string ref_attr_name = 21; // A human-readable documentation for this attribute. Markdown is allowed. optional string doc_string = 13; // The type field MUST be present for this version of the IR. // For 0.0.1 versions of the IR, this field was not defined, and // implementations needed to use has_field heuristics to determine // which value field was in use. For IR_VERSION 0.0.2 or later, this // field MUST be set and match the f|i|s|t|... field in use. This // change was made to accommodate proto3 implementations. optional AttributeType type = 20; // discriminator that indicates which field below is in use // Exactly ONE of the following fields must be present for this version of the IR optional float f = 2; // float optional int64 i = 3; // int optional bytes s = 4; // UTF-8 string optional TensorProto t = 5; // tensor value optional GraphProto g = 6; // graph optional SparseTensorProto sparse_tensor = 22; // sparse tensor value // Do not use field below, it's deprecated. // optional ValueProto v = 12; // value - subsumes everything but graph repeated float floats = 7; // list of floats repeated int64 ints = 8; // list of ints repeated bytes strings = 9; // list of UTF-8 strings repeated TensorProto tensors = 10; // list of tensors repeated GraphProto graphs = 11; // list of graph repeated SparseTensorProto sparse_tensors = 23; // list of sparse tensors } // Defines information on value, including the name, the type, and // the shape of the value. message ValueInfoProto { // This field MUST be present in this version of the IR. optional string name = 1; // namespace Value // This field MUST be present in this version of the IR for // inputs and outputs of the top-level graph. optional TypeProto type = 2; // A human-readable documentation for this value. Markdown is allowed. optional string doc_string = 3; } // Nodes // // Computation graphs are made up of a DAG of nodes, which represent what is // commonly called a "layer" or "pipeline stage" in machine learning frameworks. // // For example, it can be a node of type "Conv" that takes in an image, a filter // tensor and a bias tensor, and produces the convolved output. message NodeProto { repeated string input = 1; // namespace Value repeated string output = 2; // namespace Value // An optional identifier for this node in a graph. // This field MAY be absent in ths version of the IR. optional string name = 3; // namespace Node // The symbolic identifier of the Operator to execute. optional string op_type = 4; // namespace Operator // The domain of the OperatorSet that specifies the operator named by op_type. optional string domain = 7; // namespace Domain // Additional named attributes. repeated AttributeProto attribute = 5; // A human-readable documentation for this node. Markdown is allowed. optional string doc_string = 6; } // Training information // TrainingInfoProto stores information for training a model. // In particular, this defines two functionalities: an initialization-step // and a training-algorithm-step. Initialization resets the model // back to its original state as if no training has been consumed. // Training algorithm improves the model based on input data. // // The semantics of the initialization-step is that the initializers // in ModelProto.graph and in TrainingInfoProto.algorithm are first // initialized as specified by the initializers in the graph, and then // updated by the "initialization_binding" in every instance in // ModelProto.training_info. // // The field "algorithm" defines a computation graph which represents a // training algorithm's step. After the execution of a // TrainingInfoProto.algorithm, the initializers specified by "update_binding" // may be immediately updated. If the targeted training algorithm contains // consecutive update stages (such as block coordinate descent methods), // the user needs to create a TrainingInfoProto for each stage. message TrainingInfoProto { // This field describes a graph to compute the initial tensors // upon starting the training process. Initialization graph has no input // and can have multiple outputs. Usually, trainable tensors in neural // networks are randomly initialized. To achieve that, for each tensor, // the user can put a random number operator such as RandomNormal or // RandomUniform in TrainingInfoProto.initialization.node and assign its // random output to the specific tensor using "initialization_binding". // This graph can also set the initializers in "algorithm" in the same // TrainingInfoProto; a use case is resetting the number of training // iteration to zero. // // By default, this field is an empty graph and its evaluation does not // produce any output. optional GraphProto initialization = 1; // This field represents a training algorithm step. Given required inputs, // it computes outputs to update initializers in its own or inference graph's // initializer lists. In general, this graph contains loss node, gradient node, // optimizer node, increment of iteration count, and some calls to the inference // graph. // // The field algorithm.node is the only place the user can use GraphCall // operator. The only callable graph is the one stored in ModelProto.graph. // // By default, this field is an empty graph and its evaluation does not // produce any output. optional GraphProto algorithm = 2; // This field specifies the bindings from the outputs of "initialization" to // some initializers in "ModelProto.graph.initializer" and // the "algorithm.initializer" in the same TrainingInfoProto. // See "update_binding" below for details. // // By default, this field is empty and no initializer would be changed // by the execution of "initialization". repeated StringStringEntryProto initialization_binding = 3; // Gradient-based training is usually an iterative procedure. In one gradient // descent iteration, we apply // // x = x - r * g // // where "x" is the optimized tensor, "r" stands for learning rate, and "g" is // gradient of "x" with respect to a chosen loss. To avoid adding assignments // into the training graph, we split the update equation into // // y = x - r * g // x = y // // The user needs to save "y = x - r * g" into TrainingInfoProto.algorithm. To // tell that "y" should be assigned to "x", the field "update_binding" may // contain a key-value pair of strings, "x" (key of StringStringEntryProto) // and "y" (value of StringStringEntryProto). // For a neural network with multiple trainable (mutable) tensors, there can // be multiple key-value pairs in "update_binding". // // The initializers appears as keys in "update_binding" are considered // mutable and globally-visible variables. This implies some behaviors // as described below. // // 1. We have only unique keys in all "update_binding"s so that two global // variables may not have the same name. This ensures that one // global variable is assigned up to once. // 2. The keys must appear in names of "ModelProto.graph.initializer" or // "TrainingInfoProto.algorithm.initializer". // 3. The values must be output names of "algorithm". // 4. If an optional input of a graph is omitted when using GraphCall, the // global variable with the same name may be used. // 5. When using GraphCall, the users always can pass values to optional // inputs of the called graph even if the associated initializers appears // as keys in "update_binding"s. // 6. The graphs in TrainingInfoProto's can use global variables as // their operator inputs. // 7. Mutable variables are initialized to the value specified by the // corresponding initializer, and then potentially updated by // "initializer_binding"s and "update_binding"s in "TrainingInfoProto"s. // // This field usually contains names of trainable tensors // (in ModelProto.graph), optimizer states such as momentums in advanced // stochastic gradient methods (in TrainingInfoProto.graph), // and number of training iterations (in TrainingInfoProto.graph). // // By default, this field is empty and no initializer would be changed // by the execution of "algorithm". repeated StringStringEntryProto update_binding = 4; } // Models // // ModelProto is a top-level file/container format for bundling a ML model and // associating its computation graph with metadata. // // The semantics of the model are described by the associated GraphProto's. message ModelProto { // The version of the IR this model targets. See Version enum above. // This field MUST be present. optional int64 ir_version = 1; // The OperatorSets this model relies on. // All ModelProtos MUST have at least one entry that // specifies which version of the ONNX OperatorSet is // being imported. // // All nodes in the ModelProto's graph will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. repeated OperatorSetIdProto opset_import = 8; // The name of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. optional string producer_name = 2; // The version of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. optional string producer_version = 3; // Domain name of the model. // We use reverse domain names as name space indicators. For example: // `com.facebook.fair` or `com.microsoft.cognitiveservices` // // Together with `model_version` and GraphProto.name, this forms the unique identity of // the graph. optional string domain = 4; // The version of the graph encoded. See Version enum below. optional int64 model_version = 5; // A human-readable documentation for this model. Markdown is allowed. optional string doc_string = 6; // The parameterized graph that is evaluated to execute the model. optional GraphProto graph = 7; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 14; // Training-specific information. Sequentially executing all stored // `TrainingInfoProto.algorithm`s and assigning their outputs following // the corresponding `TrainingInfoProto.update_binding`s is one training // iteration. Similarly, to initialize the model // (as if training hasn't happened), the user should sequentially execute // all stored `TrainingInfoProto.initialization`s and assigns their outputs // using `TrainingInfoProto.initialization_binding`s. // // If this field is empty, the training behavior of the model is undefined. repeated TrainingInfoProto training_info = 20; }; // StringStringEntryProto follows the pattern for cross-proto-version maps. // See https://developers.google.com/protocol-buffers/docs/proto3#maps message StringStringEntryProto { optional string key = 1; optional string value= 2; }; message TensorAnnotation { optional string tensor_name = 1; // pairs to annotate tensor specified by above. // The keys used in the mapping below must be pre-defined in ONNX spec. // For example, for 8-bit linear quantization case, 'SCALE_TENSOR', 'ZERO_POINT_TENSOR' will be pre-defined as // quantization parameter keys. repeated StringStringEntryProto quant_parameter_tensor_names = 2; } // Graphs // // A graph defines the computational logic of a model and is comprised of a parameterized // list of nodes that form a directed acyclic graph based on their inputs and outputs. // This is the equivalent of the "network" or "graph" in many deep learning // frameworks. message GraphProto { // The nodes in the graph, sorted topologically. repeated NodeProto node = 1; // The name of the graph. optional string name = 2; // namespace Graph // A list of named tensor values, used to specify constant inputs of the graph. // Each TensorProto entry must have a distinct name (within the list) that // MAY also appear in the input list. repeated TensorProto initializer = 5; // Initializers (see above) stored in sparse format. repeated SparseTensorProto sparse_initializer = 15; // A human-readable documentation for this graph. Markdown is allowed. optional string doc_string = 10; // The inputs and outputs of the graph. repeated ValueInfoProto input = 11; repeated ValueInfoProto output = 12; // Information for the values in the graph. The ValueInfoProto.name's // must be distinct. It is optional for a value to appear in value_info list. repeated ValueInfoProto value_info = 13; // This field carries information to indicate the mapping among a tensor and its // quantization parameter tensors. For example: // For tensor 'a', it may have {'SCALE_TENSOR', 'a_scale'} and {'ZERO_POINT_TENSOR', 'a_zero_point'} annotated, // which means, tensor 'a_scale' and tensor 'a_zero_point' are scale and zero point of tensor 'a' in the model. repeated TensorAnnotation quantization_annotation = 14; // DO NOT USE the following fields, they were deprecated from earlier versions. // repeated string input = 3; // repeated string output = 4; // optional int64 ir_version = 6; // optional int64 producer_version = 7; // optional string producer_tag = 8; // optional string domain = 9; } // Tensors // // A serialized tensor value. message TensorProto { enum DataType { UNDEFINED = 0; // Basic types. FLOAT = 1; // float UINT8 = 2; // uint8_t INT8 = 3; // int8_t UINT16 = 4; // uint16_t INT16 = 5; // int16_t INT32 = 6; // int32_t INT64 = 7; // int64_t STRING = 8; // string BOOL = 9; // bool // IEEE754 half-precision floating-point format (16 bits wide). // This format has 1 sign bit, 5 exponent bits, and 10 mantissa bits. FLOAT16 = 10; DOUBLE = 11; UINT32 = 12; UINT64 = 13; COMPLEX64 = 14; // complex with float32 real and imaginary components COMPLEX128 = 15; // complex with float64 real and imaginary components // Non-IEEE floating-point format based on IEEE754 single-precision // floating-point number truncated to 16 bits. // This format has 1 sign bit, 8 exponent bits, and 7 mantissa bits. BFLOAT16 = 16; // Future extensions go here. } // The shape of the tensor. repeated int64 dims = 1; // The data type of the tensor. // This field MUST have a valid TensorProto.DataType value optional int32 data_type = 2; // For very large tensors, we may want to store them in chunks, in which // case the following fields will specify the segment that is stored in // the current TensorProto. message Segment { optional int64 begin = 1; optional int64 end = 2; } optional Segment segment = 3; // Tensor content must be organized in row-major order. // // Depending on the data_type field, exactly one of the fields below with // name ending in _data is used to store the elements of the tensor. // For float and complex64 values // Complex64 tensors are encoded as a single array of floats, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be FLOAT or COMPLEX64. repeated float float_data = 4 [packed = true]; // For int32, uint8, int8, uint16, int16, bool, and float16 values // float16 values must be bit-wise converted to an uint16_t prior // to writing to the buffer. // When this field is present, the data_type field MUST be // INT32, INT16, INT8, UINT16, UINT8, BOOL, or FLOAT16 repeated int32 int32_data = 5 [packed = true]; // For strings. // Each element of string_data is a UTF-8 encoded Unicode // string. No trailing null, no leading BOM. The protobuf "string" // scalar type is not used to match ML community conventions. // When this field is present, the data_type field MUST be STRING repeated bytes string_data = 6; // For int64. // When this field is present, the data_type field MUST be INT64 repeated int64 int64_data = 7 [packed = true]; // Optionally, a name for the tensor. optional string name = 8; // namespace Value // A human-readable documentation for this tensor. Markdown is allowed. optional string doc_string = 12; // Serializations can either use one of the fields above, or use this // raw bytes field. The only exception is the string case, where one is // required to store the content in the repeated bytes string_data field. // // When this raw_data field is used to store tensor value, elements MUST // be stored in as fixed-width, little-endian order. // Floating-point data types MUST be stored in IEEE 754 format. // Complex64 elements must be written as two consecutive FLOAT values, real component first. // Complex128 elements must be written as two consecutive DOUBLE values, real component first. // Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false). // // Note: the advantage of specific field rather than the raw_data field is // that in some cases (e.g. int data), protobuf does a better packing via // variable length storage, and may lead to smaller binary footprint. // When this field is present, the data_type field MUST NOT be STRING or UNDEFINED optional bytes raw_data = 9; // Data can be stored inside the protobuf file using type-specific fields or raw_data. // Alternatively, raw bytes data can be stored in an external file, using the external_data field. // external_data stores key-value pairs describing data location. Recognized keys are: // - "location" (required) - POSIX filesystem path relative to the directory where the ONNX // protobuf model was stored // - "offset" (optional) - position of byte at which stored data begins. Integer stored as string. // Offset values SHOULD be multiples 4096 (page size) to enable mmap support. // - "length" (optional) - number of bytes containing data. Integer stored as string. // - "checksum" (optional) - SHA1 digest of file specified in under 'location' key. repeated StringStringEntryProto external_data = 13; // Location of the data for this tensor. MUST be one of: // - DEFAULT - data stored inside the protobuf message. Data is stored in raw_data (if set) otherwise in type-specified field. // - EXTERNAL - data stored in an external location as described by external_data field. enum DataLocation { DEFAULT = 0; EXTERNAL = 1; } // If value not set, data is stored in raw_data (if set) otherwise in type-specified field. optional DataLocation data_location = 14; // For double // Complex128 tensors are encoded as a single array of doubles, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be DOUBLE or COMPLEX128 repeated double double_data = 10 [packed = true]; // For uint64 and uint32 values // When this field is present, the data_type field MUST be // UINT32 or UINT64 repeated uint64 uint64_data = 11 [packed = true]; } // A serialized sparse-tensor value message SparseTensorProto { // The sequence of non-default values are encoded as a tensor of shape [NNZ]. // The default-value is zero for numeric tensors, and empty-string for string tensors. optional TensorProto values = 1; // The indices of the non-default values, which may be stored in one of two formats. // (a) Indices can be a tensor of shape [NNZ, rank] with the [i,j]-th value // corresponding to the j-th index of the i-th value (in the values tensor). // (b) Indices can be a tensor of shape [NNZ], in which case the i-th value // must be the linearized-index of the i-th value (in the values tensor). // The linearized-index can be converted into an index tuple (k_1,...,k_rank) // using the shape provided below. // The indices must appear in ascending order without duplication. // In the first format, the ordering is lexicographic-ordering: // e.g., index-value [1,4] must appear before [2,1] optional TensorProto indices = 2; // The shape of the underlying dense-tensor: [dim_1, dim_2, ... dim_rank] repeated int64 dims = 3; } // Defines a tensor shape. A dimension can be either an integer value // or a symbolic variable. A symbolic variable represents an unknown // dimension. message TensorShapeProto { message Dimension { oneof value { int64 dim_value = 1; string dim_param = 2; // namespace Shape }; // Standard denotation can optionally be used to denote tensor // dimensions with standard semantic descriptions to ensure // that operations are applied to the correct axis of a tensor. // Refer to https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definition // for pre-defined dimension denotations. optional string denotation = 3; }; repeated Dimension dim = 1; } // Types // // The standard ONNX data types. message TypeProto { message Tensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. optional int32 elem_type = 1; optional TensorShapeProto shape = 2; } // repeated T message Sequence { // The type and optional shape of each element of the sequence. // This field MUST be present for this version of the IR. optional TypeProto elem_type = 1; }; // map message Map { // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. // This field MUST refer to an integral type ([U]INT{8|16|32|64}) or STRING optional int32 key_type = 1; // This field MUST be present for this version of the IR. optional TypeProto value_type = 2; }; oneof value { // The type of a tensor. Tensor tensor_type = 1; // NOTE: DNN-only implementations of ONNX MAY elect to not support non-tensor values // as input and output to graphs and nodes. These types are needed to naturally // support classical ML operators. DNN operators SHOULD restrict their input // and output types to tensors. // The type of a sequence. Sequence sequence_type = 4; // The type of a map. Map map_type = 5; } // An optional denotation can be used to denote the whole // type with a standard semantic description as to what is // stored inside. Refer to https://github.com/onnx/onnx/blob/master/docs/TypeDenotation.md#type-denotation-definition // for pre-defined type denotations. optional string denotation = 6; } // Operator Sets // // OperatorSets are uniquely identified by a (domain, opset_version) pair. message OperatorSetIdProto { // The domain of the operator set being identified. // The empty string ("") or absence of this field implies the operator // set that is defined as part of the ONNX specification. // This field MUST be present in this version of the IR when referring to any other operator set. optional string domain = 1; // The version of the operator set being identified. // This field MUST be present in this version of the IR. optional int64 version = 2; } // For using protobuf-lite option optimize_for = LITE_RUNTIME; onnx-1.7.0/onnx/optimizer.py0000664000000000000000000000331513655345213014570 0ustar rootroot# ATTENTION: The code in this file is highly EXPERIMENTAL. # Adventurous users should note that the APIs will probably change. """onnx optimizer This enables users to optimize their models. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import onnx import onnx.onnx_cpp2py_export.optimizer as C from onnx import ModelProto from typing import Text, Sequence, Optional """Apply the optimization on the serialized ModelProto. Arguments: input (ModelProto): model names (list of string): list of optimization names Return: return (ModelProto) optimized model Supported pass names: -- nop -- eliminate_identity -- eliminate_nop_transpose -- eliminate_nop_pad -- eliminate_unused_initializer -- fuse_consecutive_squeezes -- fuse_consecutive_transposes -- fuse_add_bias_into_conv -- fuse_transpose_into_gemm """ get_available_passes = C.get_available_passes def optimize(model, passes=None, fixed_point=False): # type: (ModelProto, Optional[Sequence[Text]], bool) -> ModelProto if passes is None: passes = ['eliminate_nop_transpose', 'eliminate_nop_pad', 'fuse_consecutive_transposes', 'fuse_transpose_into_gemm'] if not isinstance(model, ModelProto): raise ValueError('Optimizer only accepts ModelProto, incorrect type: {}'.format(type(model))) model_str = model.SerializeToString() if fixed_point: optimized_model_str = C.optimize_fixedpoint(model_str, passes) else: optimized_model_str = C.optimize(model_str, passes) return onnx.load_from_string(optimized_model_str) onnx-1.7.0/onnx/__init__.py0000664000000000000000000001410113655345213014300 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import os from .onnx_cpp2py_export import ONNX_ML from onnx.external_data_helper import load_external_data_for_model, write_external_data_tensors from .onnx_pb import * # noqa from .onnx_operators_pb import * # noqa from .version import version as __version__ # noqa # Import common subpackages so they're available when you 'import onnx' import onnx.helper # noqa import onnx.checker # noqa import onnx.defs # noqa import google.protobuf.message from typing import Union, Text, IO, Optional, cast, TypeVar, Any from six import string_types # f should be either readable or a file path def _load_bytes(f): # type: (Union[IO[bytes], Text]) -> bytes if hasattr(f, 'read') and callable(cast(IO[bytes], f).read): s = cast(IO[bytes], f).read() else: with open(cast(Text, f), 'rb') as readable: s = readable.read() return s # str should be bytes, # f should be either writable or a file path def _save_bytes(str, f): # type: (bytes, Union[IO[bytes], Text]) -> None if hasattr(f, 'write') and callable(cast(IO[bytes], f).write): cast(IO[bytes], f).write(str) else: with open(cast(Text, f), 'wb') as writable: writable.write(str) # f should be either a readable file or a file path def _get_file_path(f): # type: (Union[IO[bytes], Text]) -> Optional[Text] if isinstance(f, string_types): return os.path.abspath(f) if hasattr(f, 'name'): return os.path.abspath(f.name) return None def _serialize(proto): # type: (Union[bytes, google.protobuf.message.Message]) -> bytes ''' Serialize a in-memory proto to bytes @params proto is a in-memory proto, such as a ModelProto, TensorProto, etc @return Serialized proto in bytes ''' if isinstance(proto, bytes): return proto elif hasattr(proto, 'SerializeToString') and callable(proto.SerializeToString): result = proto.SerializeToString() return result else: raise TypeError('No SerializeToString method is detected. ' 'neither proto is a str.\ntype is {}'.format(type(proto))) _Proto = TypeVar('_Proto', bound=google.protobuf.message.Message) def _deserialize(s, proto): # type: (bytes, _Proto) -> _Proto ''' Parse bytes into a in-memory proto @params s is bytes containing serialized proto proto is a in-memory proto object @return The proto instance filled in by s ''' if not isinstance(s, bytes): raise ValueError('Parameter s must be bytes, but got type: {}'.format(type(s))) if not (hasattr(proto, 'ParseFromString') and callable(proto.ParseFromString)): raise ValueError('No ParseFromString method is detected. ' '\ntype is {}'.format(type(proto))) decoded = cast(Optional[int], proto.ParseFromString(s)) if decoded is not None and decoded != len(s): raise google.protobuf.message.DecodeError( "Protobuf decoding consumed too few bytes: {} out of {}".format( decoded, len(s))) return proto def load_model(f, format=None, load_external_data=True): # type: (Union[IO[bytes], Text], Optional[Any], bool) -> ModelProto ''' Loads a serialized ModelProto into memory @params f can be a file-like object (has "read" function) or a string containing a file name format is for future use @return Loaded in-memory ModelProto ''' s = _load_bytes(f) model = load_model_from_string(s, format=format) if load_external_data: model_filepath = _get_file_path(f) if model_filepath: base_dir = os.path.dirname(model_filepath) load_external_data_for_model(model, base_dir) return model def load_tensor(f, format=None): # type: (Union[IO[bytes], Text], Optional[Any]) -> TensorProto ''' Loads a serialized TensorProto into memory @params f can be a file-like object (has "read" function) or a string containing a file name format is for future use @return Loaded in-memory TensorProto ''' s = _load_bytes(f) return load_tensor_from_string(s, format=format) def load_model_from_string(s, format=None): # type: (bytes, Optional[Any]) -> ModelProto ''' Loads a binary string (bytes) that contains serialized ModelProto @params s is a string, which contains serialized ModelProto format is for future use @return Loaded in-memory ModelProto ''' return _deserialize(s, ModelProto()) def load_tensor_from_string(s, format=None): # type: (bytes, Optional[Any]) -> TensorProto ''' Loads a binary string (bytes) that contains serialized TensorProto @params s is a string, which contains serialized TensorProto format is for future use @return Loaded in-memory TensorProto ''' return _deserialize(s, TensorProto()) def save_model(proto, f, format=None): # type: (Union[ModelProto, bytes], Union[IO[bytes], Text], Optional[Any]) -> None ''' Saves the ModelProto to the specified path. @params proto should be a in-memory ModelProto f can be a file-like object (has "write" function) or a string containing a file name format is for future use ''' if isinstance(proto, bytes): proto = _deserialize(proto, ModelProto()) model_filepath = _get_file_path(f) if model_filepath: basepath = os.path.dirname(model_filepath) proto = write_external_data_tensors(proto, basepath) s = _serialize(proto) _save_bytes(s, f) def save_tensor(proto, f): # type: (TensorProto, Union[IO[bytes], Text]) -> None ''' Saves the TensorProto to the specified path. @params proto should be a in-memory TensorProto f can be a file-like object (has "write" function) or a string containing a file name format is for future use ''' s = _serialize(proto) _save_bytes(s, f) # For backward compatibility load = load_model load_from_string = load_model_from_string save = save_model onnx-1.7.0/onnx/onnxifi_utils.cc0000664000000000000000000000157113655345213015377 0ustar rootroot#include "onnxifi_utils.h" namespace ONNX_NAMESPACE { namespace testing { onnxTensorDescriptorV1 ProtoToOnnxTensorDescriptor( const ONNX_NAMESPACE::TensorProto& proto_tensor, std::vector>& shape_pool) { onnxTensorDescriptorV1 onnx_tensor; onnx_tensor.tag = ONNXIFI_TAG_TENSOR_DESCRIPTOR_V1; onnx_tensor.name = proto_tensor.name().c_str(); onnx_tensor.dataType = proto_tensor.data_type(); onnx_tensor.memoryType = ONNXIFI_MEMORY_TYPE_CPU; std::vector shape_values( proto_tensor.dims().begin(), proto_tensor.dims().end()); shape_pool.emplace_back(std::move(shape_values)); onnx_tensor.dimensions = (uint32_t)shape_pool.back().size(); onnx_tensor.shape = shape_pool.back().data(); onnx_tensor.buffer = (onnxPointer)proto_tensor.raw_data().data(); return onnx_tensor; } } // namespace testing } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/onnx-operators.proto30000664000000000000000000001605313655345213016345 0ustar rootroot// // WARNING: This file is automatically generated! Please edit onnx.in.proto. // // Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. syntax = "proto3"; package onnx; import "onnx/onnx.proto3"; // // This file contains the proto definitions for OperatorSetProto and // OperatorProto. OperatorSetProtos are used to describe a versioned // set of operators that can be used by a ModelProto. // // Like ModelProto, OperatorSetProto is defined as a top-level file/wire // format, however their usage is different. // // ModelProto files are used to describe executable graphs that can be // executed directly by a framework, runtime, or engine. // // OperatorSetProto files are used to describe a set of operators that are // available in a given environment. The file TBD.TBD is the OperatorSetProto // that describes the ONNX standard operators. // // Operator/function status. enum OperatorStatus { EXPERIMENTAL = 0; STABLE = 1; } message FunctionProto { // The name of the function, similar usage of op_type in OperatorProto. string name = 1; // The first version of a function set which contains this function. // When there's any breaking change for this function, the function set // contains the function needs to bump its version, and since_version of // the updated function will be changed to the updated function set version. int64 since_version = 2; // This field indicates whether the syntax, semantics, or presence // of this function is in an experimental or stable stage. Once an // function is published as STABLE, its syntax and semantics MUST NOT // change in subsequent versions of the operator set. // When a function is published as EXPERIMENTAL, the syntax and semantics // of the function MAY change across operator set versions. // Functions "become" stable by deprecating the experimental version and // introducing a new stable function with the same name. OperatorStatus status = 3; // The inputs and outputs of the function. repeated string input = 4; repeated string output = 5; // The attributes of the function. repeated string attribute= 6; // The nodes in the function. repeated NodeProto node = 7; // A human-readable documentation for this function. Markdown is allowed. string doc_string = 8; // The OperatorSets this function body (graph) relies on. // A FunctionProto body (graph) may implicitly rely on the OperatorSet that // this function belongs to. It can also explicitly rely on more OperatorSets // with this field specified. // // All nodes in the function body (graph) will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. This means at most one version can be relied // for one domain. repeated OperatorSetIdProto opset_import = 9; } // An OperatorProto represents the immutable specification of the signature // and semantics of an operator. // // Operators are declared as part of an OperatorSet, which also defines the // domain name for the set. // // Operators are uniquely identified by a three part identifier // (domain, op_type, since_version) // where // *domain* is the domain of an operator set that // contains this operator specification. // // *op_type* is the name of the operator as referenced by a // NodeProto.op_type // // *since_version* is the version of the operator set that // this operator was initially declared in. // message OperatorProto { // The name of the operator within a domain. // This field MUST be present in this version of the IR. string op_type = 1; // The version of the operator set that first introduced this // operator. This value MUST be the same value as the // opset_version of the operator set that first published this operator. // Subsequent versions of the operator set MUST NOT alter the signature // or semantics of the operator once published as STABLE. // This field MUST be present in this version of the IR. int64 since_version = 2; // This field indicates whether the syntax, semantics, or presence // of this operator is in an experimental or stable stage. Once an // operator is published as STABLE, it's syntax and semantics MUST NOT // change in subsequent versions of the operator set. // When an operator is published as EXPERIMENTAL, the syntax and semantics // of the operator MAY change across operator set versions. // Operators "become" stable by deprecating the experimental version and // introducing a new stable operator with the same op_type. OperatorStatus status = 3; // Eventually we will declare the signature of the operator here // A human-readable documentation for this operator. Markdown is allowed. string doc_string = 10; } // An OperatorSetProto represents an immutable set of immutable operator // specifications. // // The domain of the set (OperatorSetProto.domain) is a reverse-DNS name // that disambiguates operator sets defined by independent entities. // // The version of the set (opset_version) is a monotonically increasing // integer that indicates changes to the membership of the operator set. // // // Operator sets are uniquely identified by a two part identifier (domain, opset_version) // // Like ModelProto, OperatorSetProto is intended as a top-level file/wire format, // and thus has the standard format headers in addition to the operator set information. // message OperatorSetProto { // All OperatorSetProtos start with a distingushed byte sequence to disambiguate // protobuf files containing OperatorSets from other content. // This field MUST be "ONNXOPSET" // This field MUST be present in this version of the IR string magic = 1; // All OperatorSetProtos indicate the version of the IR syntax and semantics // they adhere to. It is always IR_VERSION. // This field MUST be present in this version of the IR int64 ir_version = 2; // The prerelease component of the SemVer of the IR. // This field MAY be absent in this version of the IR string ir_version_prerelease = 3; // The build metadata component of the SemVer of the IR. // This field MAY be absent in this version of the IR string ir_build_metadata = 7; // Domain name of the operator set, in reverse DNS form (e.g., com.acme.dnnops). string domain = 4; // The version of the set of operators. This is a simple int value // that is monotonically increasing as new versions of operator set // are published. All operators in this set MUST have version // numbers no greater than opset_version. int64 opset_version = 5; // A human-readable documentation for this set of operators. Markdown is allowed. string doc_string = 6; // The operators specified by this operator set. // The (name, version) MUST be unique across all OperatorProtos in operator repeated OperatorProto operator = 8; // The functions specified by this operator set. // The (name, version) MUST be unique across all OperatorProtos/FunctionProtos in operator/functions repeated FunctionProto functions = 9; } // For using protobuf-lite option optimize_for = LITE_RUNTIME; onnx-1.7.0/onnx/helper.py0000664000000000000000000005274513655345213014040 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import collections import numbers from six import text_type, integer_types, binary_type import google.protobuf.message from onnx import TensorProto, SparseTensorProto, AttributeProto, ValueInfoProto, TensorShapeProto, \ NodeProto, ModelProto, GraphProto, OperatorSetIdProto, TypeProto, IR_VERSION import onnx.defs as defs from onnx import mapping from onnx.mapping import STORAGE_TENSOR_TYPE_TO_FIELD from typing import Text, Sequence, Any, Optional, Dict, Union, TypeVar, Callable, Tuple, List, cast import numpy as np # type: ignore def make_node( op_type, # type: Text inputs, # type: Sequence[Text] outputs, # type: Sequence[Text] name=None, # type: Optional[Text] doc_string=None, # type: Optional[Text] domain=None, # type: Optional[Text] **kwargs # type: Any ): # type: (...) -> NodeProto """Construct a NodeProto. Arguments: op_type (string): The name of the operator to construct inputs (list of string): list of input names outputs (list of string): list of output names name (string, default None): optional unique identifier for NodeProto doc_string (string, default None): optional documentation string for NodeProto domain (string, default None): optional domain for NodeProto. If it's None, we will just use default domain (which is empty) **kwargs (dict): the attributes of the node. The acceptable values are documented in :func:`make_attribute`. """ node = NodeProto() node.op_type = op_type node.input.extend(inputs) node.output.extend(outputs) if name: node.name = name if doc_string: node.doc_string = doc_string if domain is not None: node.domain = domain if kwargs: node.attribute.extend( make_attribute(key, value) for key, value in sorted(kwargs.items())) return node def make_operatorsetid( domain, # type: Text version, # type: int ): # type: (...) -> OperatorSetIdProto """Construct an OperatorSetIdProto. Arguments: domain (string): The domain of the operator set id version (integer): Version of operator set id """ operatorsetid = OperatorSetIdProto() operatorsetid.domain = domain operatorsetid.version = version return operatorsetid def make_graph( nodes, # type: Sequence[NodeProto] name, # type: Text inputs, # type: Sequence[ValueInfoProto] outputs, # type: Sequence[ValueInfoProto] initializer=None, # type: Optional[Sequence[TensorProto]] doc_string=None, # type: Optional[Text] value_info=[], # type: Sequence[ValueInfoProto] ): # type: (...) -> GraphProto if initializer is None: initializer = [] if value_info is None: value_info = [] graph = GraphProto() graph.node.extend(nodes) graph.name = name graph.input.extend(inputs) graph.output.extend(outputs) graph.initializer.extend(initializer) graph.value_info.extend(value_info) if doc_string: graph.doc_string = doc_string return graph def make_opsetid(domain, version): # type: (Text, int) -> OperatorSetIdProto opsetid = OperatorSetIdProto() opsetid.domain = domain opsetid.version = version return opsetid def make_model(graph, **kwargs): # type: (GraphProto, **Any) -> ModelProto model = ModelProto() # Touch model.ir_version so it is stored as the version from which it is # generated. model.ir_version = IR_VERSION model.graph.CopyFrom(graph) opset_imports = None # type: Optional[Sequence[OperatorSetIdProto]] opset_imports = kwargs.pop('opset_imports', None) # type: ignore if opset_imports is not None: model.opset_import.extend(opset_imports) else: # Default import imp = model.opset_import.add() imp.version = defs.onnx_opset_version() for k, v in kwargs.items(): # TODO: Does this work with repeated fields? setattr(model, k, v) return model def set_model_props(model, dict_value): # type: (ModelProto, Dict[Text, Text]) -> None del model.metadata_props[:] for (k, v) in dict_value.items(): entry = model.metadata_props.add() entry.key = k entry.value = v # model.metadata_properties.append(entry) def split_complex_to_pairs(ca): # type: (Sequence[np.complex64]) -> Sequence[int] return [(ca[i // 2].real if (i % 2 == 0) else ca[i // 2].imag) for i in range(len(ca) * 2)] def make_tensor( name, # type: Text data_type, # type: int dims, # type: Sequence[int] vals, # type: Any raw=False # type: bool ): # type: (...) -> TensorProto ''' Make a TensorProto with specified arguments. If raw is False, this function will choose the corresponding proto field to store the values based on data_type. If raw is True, use "raw_data" proto field to store the values, and values should be of type bytes in this case. ''' tensor = TensorProto() tensor.data_type = data_type tensor.name = name if data_type == TensorProto.STRING: assert not raw, "Can not use raw_data to store string type" if (data_type == TensorProto.COMPLEX64 or data_type == TensorProto.COMPLEX128): vals = split_complex_to_pairs(vals) if raw: tensor.raw_data = vals else: field = mapping.STORAGE_TENSOR_TYPE_TO_FIELD[ mapping.TENSOR_TYPE_TO_STORAGE_TENSOR_TYPE[data_type]] getattr(tensor, field).extend(vals) tensor.dims.extend(dims) return tensor def make_sparse_tensor( values, # type: TensorProto indices, # type: TensorProto dims # type: Sequence[int] ): # type: (...) -> SparseTensorProto sparse = SparseTensorProto() sparse.values.CopyFrom(values) sparse.indices.CopyFrom(indices) sparse.dims.extend(dims) return sparse def _to_bytes_or_false(val): # type: (Union[Text, bytes]) -> Union[bytes, bool] """An internal graph to convert the input to a bytes or to False. The criteria for conversion is as follows and should be python 2 and 3 compatible: - If val is py2 str or py3 bytes: return bytes - If val is py2 unicode or py3 str: return val.decode('utf-8') - Otherwise, return False """ if isinstance(val, bytes): return val try: return val.encode('utf-8') except AttributeError: return False def make_attribute( key, # type: Text value, # type: Any doc_string=None # type: Optional[Text] ): # type: (...) -> AttributeProto """Makes an AttributeProto based on the value type.""" attr = AttributeProto() attr.name = key if doc_string: attr.doc_string = doc_string is_iterable = isinstance(value, collections.Iterable) bytes_or_false = _to_bytes_or_false(value) # First, singular cases # float if isinstance(value, float): attr.f = value attr.type = AttributeProto.FLOAT # integer elif isinstance(value, numbers.Integral): attr.i = cast(int, value) attr.type = AttributeProto.INT # string elif bytes_or_false is not False: assert isinstance(bytes_or_false, bytes) attr.s = bytes_or_false attr.type = AttributeProto.STRING elif isinstance(value, TensorProto): attr.t.CopyFrom(value) attr.type = AttributeProto.TENSOR elif isinstance(value, SparseTensorProto): attr.sparse_tensor.CopyFrom(value) attr.type = AttributeProto.SPARSE_TENSOR elif isinstance(value, GraphProto): attr.g.CopyFrom(value) attr.type = AttributeProto.GRAPH # third, iterable cases elif is_iterable: byte_array = [_to_bytes_or_false(v) for v in value] if all(isinstance(v, float) for v in value): attr.floats.extend(value) attr.type = AttributeProto.FLOATS elif all(isinstance(v, numbers.Integral) for v in value): # Turn np.int32/64 into Python built-in int. attr.ints.extend(int(v) for v in value) attr.type = AttributeProto.INTS elif all(map(lambda bytes_or_false: bytes_or_false is not False, byte_array)): attr.strings.extend(cast(List[bytes], byte_array)) attr.type = AttributeProto.STRINGS elif all(isinstance(v, TensorProto) for v in value): attr.tensors.extend(value) attr.type = AttributeProto.TENSORS elif all(isinstance(v, SparseTensorProto) for v in value): attr.sparse_tensors.extend(value) attr.type = AttributeProto.SPARSE_TENSORS elif all(isinstance(v, GraphProto) for v in value): attr.graphs.extend(value) attr.type = AttributeProto.GRAPHS else: raise ValueError( "You passed in an iterable attribute but I cannot figure out " "its applicable type.") else: raise TypeError( 'value "{}" is not valid attribute data type.'.format(value)) return attr def get_attribute_value(attr): # type: (AttributeProto) -> Any if attr.type == AttributeProto.FLOAT: return attr.f if attr.type == AttributeProto.INT: return attr.i if attr.type == AttributeProto.STRING: return attr.s if attr.type == AttributeProto.TENSOR: return attr.t if attr.type == AttributeProto.GRAPH: return attr.g if attr.type == AttributeProto.FLOATS: return list(attr.floats) if attr.type == AttributeProto.INTS: return list(attr.ints) if attr.type == AttributeProto.STRINGS: return list(attr.strings) if attr.type == AttributeProto.TENSORS: return list(attr.tensors) if attr.type == AttributeProto.GRAPHS: return list(attr.graphs) raise ValueError("Unsupported ONNX attribute: {}".format(attr)) def make_empty_tensor_value_info(name): # type: (Text) -> ValueInfoProto value_info_proto = ValueInfoProto() value_info_proto.name = name return value_info_proto def make_tensor_value_info( name, # type: Text elem_type, # type: int shape, # type: Optional[Sequence[Union[Text, int]]] doc_string="", # type: Text shape_denotation=None, # type: Optional[List[Text]] ): # type: (...) -> ValueInfoProto """Makes a ValueInfoProto based on the data type and shape.""" value_info_proto = ValueInfoProto() value_info_proto.name = name if doc_string: value_info_proto.doc_string = doc_string tensor_type_proto = value_info_proto.type.tensor_type tensor_type_proto.elem_type = elem_type tensor_shape_proto = tensor_type_proto.shape if shape is not None: # You might think this is a no-op (extending a normal Python # list by [] certainly is), but protobuf lists work a little # differently; if a field is never set, it is omitted from the # resulting protobuf; a list that is explicitly set to be # empty will get an (empty) entry in the protobuf. This # difference is visible to our consumers, so make sure we emit # an empty shape! tensor_shape_proto.dim.extend([]) if shape_denotation: if len(shape_denotation) != len(shape): raise ValueError( 'Invalid shape_denotation. ' 'Must be of the same length as shape.') for i, d in enumerate(shape): dim = tensor_shape_proto.dim.add() if d is None: pass elif isinstance(d, integer_types): dim.dim_value = d elif isinstance(d, text_type): dim.dim_param = d else: raise ValueError( 'Invalid item in shape: {}. ' 'Needs to of integer_types or text_type.'.format(d)) if shape_denotation: dim.denotation = shape_denotation[i] return value_info_proto def make_sequence_value_info( name, # type: Text elem_type, # type: int shape, # type: Optional[Sequence[Union[Text, int]]] doc_string="", # type: Text elem_shape_denotation=None, # type: Optional[List[Text]] ): # type: (...) -> ValueInfoProto """Makes a ValueInfoProto based on the data type and shape for Sequence.""" value_info_proto = ValueInfoProto() value_info_proto.name = name if doc_string: value_info_proto.doc_string = doc_string sequence_type_proto = value_info_proto.type.sequence_type sequence_type_proto.elem_type.tensor_type.elem_type = elem_type tensor_value_info = make_tensor_value_info(name, elem_type, shape, doc_string, elem_shape_denotation) if shape is not None: sequence_type_proto.elem_type.tensor_type.shape.CopyFrom(tensor_value_info.type.tensor_type.shape) return value_info_proto def _sanitize_str(s): # type: (Union[Text, bytes]) -> Text if isinstance(s, text_type): sanitized = s elif isinstance(s, binary_type): sanitized = s.decode('utf-8', errors='ignore') else: sanitized = str(s) if len(sanitized) < 64: return sanitized return sanitized[:64] + '...<+len=%d>' % (len(sanitized) - 64) def printable_attribute(attr, subgraphs=False): # type: (AttributeProto, bool) -> Union[Text, Tuple[Text, List[GraphProto]]] content = [] content.append(attr.name) content.append("=") def str_float(f): # type: (float) -> Text # NB: Different Python versions print different numbers of trailing # decimals, specifying this explicitly keeps it consistent for all # versions return '{:.15g}'.format(f) def str_int(i): # type: (int) -> Text # NB: In Python 2, longs will repr() as '2L', which is ugly and # unnecessary. Explicitly format it to keep it consistent. return '{:d}'.format(i) def str_str(s): # type: (Text) -> Text return repr(s) _T = TypeVar('_T') # noqa def str_list(str_elem, xs): # type: (Callable[[_T], Text], Sequence[_T]) -> Text return '[' + ', '.join(map(str_elem, xs)) + ']' # for now, this logic should continue to work as long as we are running on a proto3 # implementation. If/when we switch to proto3, we will need to use attr.type # To support printing subgraphs, if we find a graph attribute, print out # its name here and pass the graph itself up to the caller for later # printing. graphs = [] if attr.HasField("f"): content.append(str_float(attr.f)) elif attr.HasField("i"): content.append(str_int(attr.i)) elif attr.HasField("s"): # TODO: Bit nervous about Python 2 / Python 3 determinism implications content.append(repr(_sanitize_str(attr.s))) elif attr.HasField("t"): if len(attr.t.dims) > 0: content.append("") else: # special case to print scalars field = STORAGE_TENSOR_TYPE_TO_FIELD[attr.t.data_type] content.append(''.format(str(getattr(attr.t, field)))) elif attr.HasField("g"): content.append("".format(attr.g.name)) graphs.append(attr.g) elif attr.floats: content.append(str_list(str_float, attr.floats)) elif attr.ints: content.append(str_list(str_int, attr.ints)) elif attr.strings: # TODO: Bit nervous about Python 2 / Python 3 determinism implications content.append(str(list(map(_sanitize_str, attr.strings)))) elif attr.tensors: content.append("[, ...]") elif attr.graphs: content.append('[') for i, g in enumerate(attr.graphs): comma = ',' if i != len(attr.graphs) - 1 else '' content.append('{}'.format(g.name, comma)) content.append(']') graphs.extend(attr.graphs) else: content.append("") if subgraphs: return ' '.join(content), graphs else: return ' '.join(content) def printable_dim(dim): # type: (TensorShapeProto.Dimension) -> Text which = dim.WhichOneof('value') assert which is not None return str(getattr(dim, which)) def printable_type(t): # type: (TypeProto) -> Text if t.WhichOneof('value') == "tensor_type": s = TensorProto.DataType.Name(t.tensor_type.elem_type) if t.tensor_type.HasField('shape'): if len(t.tensor_type.shape.dim): s += str(', ' + 'x'.join(map(printable_dim, t.tensor_type.shape.dim))) else: s += str(', scalar') return s if t.WhichOneof('value') is None: return "" return 'Unknown type {}'.format(t.WhichOneof('value')) def printable_value_info(v): # type: (ValueInfoProto) -> Text s = '%{}'.format(v.name) if v.type: s = '{}[{}]'.format(s, printable_type(v.type)) return s def printable_tensor_proto(t): # type: (TensorProto) -> Text s = '%{}['.format(t.name) s += TensorProto.DataType.Name(t.data_type) if t.dims is not None: if len(t.dims): s += str(', ' + 'x'.join(map(str, t.dims))) else: s += str(', scalar') s += ']' return s def printable_node(node, prefix='', subgraphs=False): # type: (NodeProto, Text, bool) -> Union[Text, Tuple[Text, List[GraphProto]]] content = [] if len(node.output): content.append( ', '.join(['%{}'.format(name) for name in node.output])) content.append('=') # To deal with nested graphs graphs = [] # type: List[GraphProto] printed_attrs = [] for attr in node.attribute: if subgraphs: printed_attr, gs = printable_attribute(attr, subgraphs) assert isinstance(gs, list) graphs.extend(gs) printed_attrs.append(printed_attr) else: printed = printable_attribute(attr) assert isinstance(printed, Text) printed_attrs.append(printed) printed_attributes = ', '.join(sorted(printed_attrs)) printed_inputs = ', '.join(['%{}'.format(name) for name in node.input]) if node.attribute: content.append("{}[{}]({})".format(node.op_type, printed_attributes, printed_inputs)) else: content.append("{}({})".format(node.op_type, printed_inputs)) if subgraphs: return prefix + ' '.join(content), graphs else: return prefix + ' '.join(content) def printable_graph(graph, prefix=''): # type: (GraphProto, Text) -> Text content = [] indent = prefix + ' ' # header header = ['graph', graph.name] initializers = {t.name for t in graph.initializer} if len(graph.input): header.append("(") in_strs = [] # required inputs in_with_init_strs = [] # optional inputs with initializer providing default value for inp in graph.input: if inp.name not in initializers: in_strs.append(printable_value_info(inp)) else: in_with_init_strs.append(printable_value_info(inp)) if in_strs: content.append(prefix + ' '.join(header)) header = [] for line in in_strs: content.append(prefix + ' ' + line) header.append(")") if in_with_init_strs: header.append("optional inputs with matching initializers (") content.append(prefix + ' '.join(header)) header = [] for line in in_with_init_strs: content.append(prefix + ' ' + line) header.append(")") # from IR 4 onwards an initializer is not required to have a matching graph input # so output the name, type and shape of those as well if len(in_with_init_strs) < len(initializers): graph_inputs = {i.name for i in graph.input} init_strs = [printable_tensor_proto(i) for i in graph.initializer if i.name not in graph_inputs] header.append("initializers (") content.append(prefix + ' '.join(header)) header = [] for line in init_strs: content.append(prefix + ' ' + line) header.append(")") header.append('{') content.append(prefix + ' '.join(header)) graphs = [] # type: List[GraphProto] # body for node in graph.node: pn, gs = printable_node(node, indent, subgraphs=True) assert isinstance(gs, list) content.append(pn) graphs.extend(gs) # tail tail = ['return'] if len(graph.output): tail.append( ', '.join(['%{}'.format(out.name) for out in graph.output])) content.append(indent + ' '.join(tail)) # closing bracket content.append(prefix + '}') for g in graphs: content.append('\n' + printable_graph(g)) return '\n'.join(content) def strip_doc_string(proto): # type: (google.protobuf.message.Message) -> None """ Empties `doc_string` field on any nested protobuf messages """ assert isinstance(proto, google.protobuf.message.Message) for descriptor in proto.DESCRIPTOR.fields: if descriptor.name == 'doc_string': proto.ClearField(descriptor.name) elif descriptor.type == descriptor.TYPE_MESSAGE: if descriptor.label == descriptor.LABEL_REPEATED: for x in getattr(proto, descriptor.name): strip_doc_string(x) elif proto.HasField(descriptor.name): strip_doc_string(getattr(proto, descriptor.name)) onnx-1.7.0/onnx/onnx-ml.proto30000664000000000000000000007215513655345213014744 0ustar rootroot// // WARNING: This file is automatically generated! Please edit onnx.in.proto. // // Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. syntax = "proto3"; package onnx; // Overview // // ONNX is an open specification that is comprised of the following components: // // 1) A definition of an extensible computation graph model. // 2) Definitions of standard data types. // 3) Definitions of built-in operators. // // This document describes the syntax of models and their computation graphs, // as well as the standard data types. Together, they are referred to as the ONNX // Intermediate Representation, or 'IR' for short. // // The normative semantic specification of the ONNX IR is found in docs/IR.md. // Definitions of the built-in neural network operators may be found in docs/Operators.md. // Definitions of the built-in classical machine learning operators may be found in // docs/Operators-ml.md. // Notes // // Release // // We are still in the very early stage of defining ONNX. The current // version of ONNX is a starting point. While we are actively working // towards a complete spec, we would like to get the community involved // by sharing our working version of ONNX. // // Protobuf compatibility // // To simplify framework compatibility, ONNX is defined using the subset of protobuf // that is compatible with both protobuf v2 and v3. This means that we do not use any // protobuf features that are only available in one of the two versions. // // Here are the most notable contortions we have to carry out to work around // these limitations: // // - No 'map' (added protobuf 3.0). We instead represent mappings as lists // of key-value pairs, where order does not matter and duplicates // are not allowed. // Versioning // // ONNX versioning is specified in docs/IR.md and elaborated on in docs/Versioning.md // // To be compatible with both proto2 and proto3, we will use a version number // that is not defined by the default value but an explicit enum number. enum Version { // proto3 requires the first enum value to be zero. // We add this just to appease the compiler. _START_VERSION = 0; // The version field is always serialized and we will use it to store the // version that the graph is generated from. This helps us set up version // control. // For the IR, we are using simple numbers starting with 0x00000001, // which was the version we published on Oct 10, 2017. IR_VERSION_2017_10_10 = 0x0000000000000001; // IR_VERSION 2 published on Oct 30, 2017 // - Added type discriminator to AttributeProto to support proto3 users IR_VERSION_2017_10_30 = 0x0000000000000002; // IR VERSION 3 published on Nov 3, 2017 // - For operator versioning: // - Added new message OperatorSetIdProto // - Added opset_import in ModelProto // - For vendor extensions, added domain in NodeProto IR_VERSION_2017_11_3 = 0x0000000000000003; // IR VERSION 4 published on Jan 22, 2019 // - Relax constraint that initializers should be a subset of graph inputs // - Add type BFLOAT16 IR_VERSION_2019_1_22 = 0x0000000000000004; // IR VERSION 5 published on March 18, 2019 // - Add message TensorAnnotation. // - Add quantization annotation in GraphProto to map tensor with its scale and zero point quantization parameters. IR_VERSION_2019_3_18 = 0x0000000000000005; // IR VERSION 6 published on Sep 19, 2019 // - Add support for sparse tensor constants stored in model. // - Add message SparseTensorProto // - Add sparse initializers IR_VERSION_2019_9_19 = 0x0000000000000006; // IR VERSION 7 published on // - Add support to allow function body graph to rely on multiple external opreator sets. // - Add a list to promote inference graph's initializers to global and // mutable variables. Global variables are visible in all graphs of the // stored models. // - Add message TrainingInfoProto to store initialization // method and training algorithm. The execution of TrainingInfoProto // can modify the values of mutable variables. // - Make inference graph callable from TrainingInfoProto via GraphCall operator. IR_VERSION = 0x0000000000000007; } // Attributes // // A named attribute containing either singular float, integer, string, graph, // and tensor values, or repeated float, integer, string, graph, and tensor values. // An AttributeProto MUST contain the name field, and *only one* of the // following content fields, effectively enforcing a C/C++ union equivalent. message AttributeProto { // Note: this enum is structurally identical to the OpSchema::AttrType // enum defined in schema.h. If you rev one, you likely need to rev the other. enum AttributeType { UNDEFINED = 0; FLOAT = 1; INT = 2; STRING = 3; TENSOR = 4; GRAPH = 5; SPARSE_TENSOR = 11; FLOATS = 6; INTS = 7; STRINGS = 8; TENSORS = 9; GRAPHS = 10; SPARSE_TENSORS = 12; } // The name field MUST be present for this version of the IR. string name = 1; // namespace Attribute // if ref_attr_name is not empty, ref_attr_name is the attribute name in parent function. // In this case, this AttributeProto does not contain data, and it's a reference of attribute // in parent scope. // NOTE: This should ONLY be used in function (sub-graph). It's invalid to be used in main graph. string ref_attr_name = 21; // A human-readable documentation for this attribute. Markdown is allowed. string doc_string = 13; // The type field MUST be present for this version of the IR. // For 0.0.1 versions of the IR, this field was not defined, and // implementations needed to use has_field heuristics to determine // which value field was in use. For IR_VERSION 0.0.2 or later, this // field MUST be set and match the f|i|s|t|... field in use. This // change was made to accommodate proto3 implementations. AttributeType type = 20; // discriminator that indicates which field below is in use // Exactly ONE of the following fields must be present for this version of the IR float f = 2; // float int64 i = 3; // int bytes s = 4; // UTF-8 string TensorProto t = 5; // tensor value GraphProto g = 6; // graph SparseTensorProto sparse_tensor = 22; // sparse tensor value // Do not use field below, it's deprecated. // optional ValueProto v = 12; // value - subsumes everything but graph repeated float floats = 7; // list of floats repeated int64 ints = 8; // list of ints repeated bytes strings = 9; // list of UTF-8 strings repeated TensorProto tensors = 10; // list of tensors repeated GraphProto graphs = 11; // list of graph repeated SparseTensorProto sparse_tensors = 23; // list of sparse tensors } // Defines information on value, including the name, the type, and // the shape of the value. message ValueInfoProto { // This field MUST be present in this version of the IR. string name = 1; // namespace Value // This field MUST be present in this version of the IR for // inputs and outputs of the top-level graph. TypeProto type = 2; // A human-readable documentation for this value. Markdown is allowed. string doc_string = 3; } // Nodes // // Computation graphs are made up of a DAG of nodes, which represent what is // commonly called a "layer" or "pipeline stage" in machine learning frameworks. // // For example, it can be a node of type "Conv" that takes in an image, a filter // tensor and a bias tensor, and produces the convolved output. message NodeProto { repeated string input = 1; // namespace Value repeated string output = 2; // namespace Value // An optional identifier for this node in a graph. // This field MAY be absent in ths version of the IR. string name = 3; // namespace Node // The symbolic identifier of the Operator to execute. string op_type = 4; // namespace Operator // The domain of the OperatorSet that specifies the operator named by op_type. string domain = 7; // namespace Domain // Additional named attributes. repeated AttributeProto attribute = 5; // A human-readable documentation for this node. Markdown is allowed. string doc_string = 6; } // Training information // TrainingInfoProto stores information for training a model. // In particular, this defines two functionalities: an initialization-step // and a training-algorithm-step. Initialization resets the model // back to its original state as if no training has been consumed. // Training algorithm improves the model based on input data. // // The semantics of the initialization-step is that the initializers // in ModelProto.graph and in TrainingInfoProto.algorithm are first // initialized as specified by the initializers in the graph, and then // updated by the "initialization_binding" in every instance in // ModelProto.training_info. // // The field "algorithm" defines a computation graph which represents a // training algorithm's step. After the execution of a // TrainingInfoProto.algorithm, the initializers specified by "update_binding" // may be immediately updated. If the targeted training algorithm contains // consecutive update stages (such as block coordinate descent methods), // the user needs to create a TrainingInfoProto for each stage. message TrainingInfoProto { // This field describes a graph to compute the initial tensors // upon starting the training process. Initialization graph has no input // and can have multiple outputs. Usually, trainable tensors in neural // networks are randomly initialized. To achieve that, for each tensor, // the user can put a random number operator such as RandomNormal or // RandomUniform in TrainingInfoProto.initialization.node and assign its // random output to the specific tensor using "initialization_binding". // This graph can also set the initializers in "algorithm" in the same // TrainingInfoProto; a use case is resetting the number of training // iteration to zero. // // By default, this field is an empty graph and its evaluation does not // produce any output. GraphProto initialization = 1; // This field represents a training algorithm step. Given required inputs, // it computes outputs to update initializers in its own or inference graph's // initializer lists. In general, this graph contains loss node, gradient node, // optimizer node, increment of iteration count, and some calls to the inference // graph. // // The field algorithm.node is the only place the user can use GraphCall // operator. The only callable graph is the one stored in ModelProto.graph. // // By default, this field is an empty graph and its evaluation does not // produce any output. GraphProto algorithm = 2; // This field specifies the bindings from the outputs of "initialization" to // some initializers in "ModelProto.graph.initializer" and // the "algorithm.initializer" in the same TrainingInfoProto. // See "update_binding" below for details. // // By default, this field is empty and no initializer would be changed // by the execution of "initialization". repeated StringStringEntryProto initialization_binding = 3; // Gradient-based training is usually an iterative procedure. In one gradient // descent iteration, we apply // // x = x - r * g // // where "x" is the optimized tensor, "r" stands for learning rate, and "g" is // gradient of "x" with respect to a chosen loss. To avoid adding assignments // into the training graph, we split the update equation into // // y = x - r * g // x = y // // The user needs to save "y = x - r * g" into TrainingInfoProto.algorithm. To // tell that "y" should be assigned to "x", the field "update_binding" may // contain a key-value pair of strings, "x" (key of StringStringEntryProto) // and "y" (value of StringStringEntryProto). // For a neural network with multiple trainable (mutable) tensors, there can // be multiple key-value pairs in "update_binding". // // The initializers appears as keys in "update_binding" are considered // mutable and globally-visible variables. This implies some behaviors // as described below. // // 1. We have only unique keys in all "update_binding"s so that two global // variables may not have the same name. This ensures that one // global variable is assigned up to once. // 2. The keys must appear in names of "ModelProto.graph.initializer" or // "TrainingInfoProto.algorithm.initializer". // 3. The values must be output names of "algorithm". // 4. If an optional input of a graph is omitted when using GraphCall, the // global variable with the same name may be used. // 5. When using GraphCall, the users always can pass values to optional // inputs of the called graph even if the associated initializers appears // as keys in "update_binding"s. // 6. The graphs in TrainingInfoProto's can use global variables as // their operator inputs. // 7. Mutable variables are initialized to the value specified by the // corresponding initializer, and then potentially updated by // "initializer_binding"s and "update_binding"s in "TrainingInfoProto"s. // // This field usually contains names of trainable tensors // (in ModelProto.graph), optimizer states such as momentums in advanced // stochastic gradient methods (in TrainingInfoProto.graph), // and number of training iterations (in TrainingInfoProto.graph). // // By default, this field is empty and no initializer would be changed // by the execution of "algorithm". repeated StringStringEntryProto update_binding = 4; } // Models // // ModelProto is a top-level file/container format for bundling a ML model and // associating its computation graph with metadata. // // The semantics of the model are described by the associated GraphProto's. message ModelProto { // The version of the IR this model targets. See Version enum above. // This field MUST be present. int64 ir_version = 1; // The OperatorSets this model relies on. // All ModelProtos MUST have at least one entry that // specifies which version of the ONNX OperatorSet is // being imported. // // All nodes in the ModelProto's graph will bind against the operator // with the same-domain/same-op_type operator with the HIGHEST version // in the referenced operator sets. repeated OperatorSetIdProto opset_import = 8; // The name of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. string producer_name = 2; // The version of the framework or tool used to generate this model. // This field SHOULD be present to indicate which implementation/tool/framework // emitted the model. string producer_version = 3; // Domain name of the model. // We use reverse domain names as name space indicators. For example: // `com.facebook.fair` or `com.microsoft.cognitiveservices` // // Together with `model_version` and GraphProto.name, this forms the unique identity of // the graph. string domain = 4; // The version of the graph encoded. See Version enum below. int64 model_version = 5; // A human-readable documentation for this model. Markdown is allowed. string doc_string = 6; // The parameterized graph that is evaluated to execute the model. GraphProto graph = 7; // Named metadata values; keys should be distinct. repeated StringStringEntryProto metadata_props = 14; // Training-specific information. Sequentially executing all stored // `TrainingInfoProto.algorithm`s and assigning their outputs following // the corresponding `TrainingInfoProto.update_binding`s is one training // iteration. Similarly, to initialize the model // (as if training hasn't happened), the user should sequentially execute // all stored `TrainingInfoProto.initialization`s and assigns their outputs // using `TrainingInfoProto.initialization_binding`s. // // If this field is empty, the training behavior of the model is undefined. repeated TrainingInfoProto training_info = 20; }; // StringStringEntryProto follows the pattern for cross-proto-version maps. // See https://developers.google.com/protocol-buffers/docs/proto3#maps message StringStringEntryProto { string key = 1; string value= 2; }; message TensorAnnotation { string tensor_name = 1; // pairs to annotate tensor specified by above. // The keys used in the mapping below must be pre-defined in ONNX spec. // For example, for 8-bit linear quantization case, 'SCALE_TENSOR', 'ZERO_POINT_TENSOR' will be pre-defined as // quantization parameter keys. repeated StringStringEntryProto quant_parameter_tensor_names = 2; } // Graphs // // A graph defines the computational logic of a model and is comprised of a parameterized // list of nodes that form a directed acyclic graph based on their inputs and outputs. // This is the equivalent of the "network" or "graph" in many deep learning // frameworks. message GraphProto { // The nodes in the graph, sorted topologically. repeated NodeProto node = 1; // The name of the graph. string name = 2; // namespace Graph // A list of named tensor values, used to specify constant inputs of the graph. // Each TensorProto entry must have a distinct name (within the list) that // MAY also appear in the input list. repeated TensorProto initializer = 5; // Initializers (see above) stored in sparse format. repeated SparseTensorProto sparse_initializer = 15; // A human-readable documentation for this graph. Markdown is allowed. string doc_string = 10; // The inputs and outputs of the graph. repeated ValueInfoProto input = 11; repeated ValueInfoProto output = 12; // Information for the values in the graph. The ValueInfoProto.name's // must be distinct. It is optional for a value to appear in value_info list. repeated ValueInfoProto value_info = 13; // This field carries information to indicate the mapping among a tensor and its // quantization parameter tensors. For example: // For tensor 'a', it may have {'SCALE_TENSOR', 'a_scale'} and {'ZERO_POINT_TENSOR', 'a_zero_point'} annotated, // which means, tensor 'a_scale' and tensor 'a_zero_point' are scale and zero point of tensor 'a' in the model. repeated TensorAnnotation quantization_annotation = 14; // DO NOT USE the following fields, they were deprecated from earlier versions. // repeated string input = 3; // repeated string output = 4; // optional int64 ir_version = 6; // optional int64 producer_version = 7; // optional string producer_tag = 8; // optional string domain = 9; } // Tensors // // A serialized tensor value. message TensorProto { enum DataType { UNDEFINED = 0; // Basic types. FLOAT = 1; // float UINT8 = 2; // uint8_t INT8 = 3; // int8_t UINT16 = 4; // uint16_t INT16 = 5; // int16_t INT32 = 6; // int32_t INT64 = 7; // int64_t STRING = 8; // string BOOL = 9; // bool // IEEE754 half-precision floating-point format (16 bits wide). // This format has 1 sign bit, 5 exponent bits, and 10 mantissa bits. FLOAT16 = 10; DOUBLE = 11; UINT32 = 12; UINT64 = 13; COMPLEX64 = 14; // complex with float32 real and imaginary components COMPLEX128 = 15; // complex with float64 real and imaginary components // Non-IEEE floating-point format based on IEEE754 single-precision // floating-point number truncated to 16 bits. // This format has 1 sign bit, 8 exponent bits, and 7 mantissa bits. BFLOAT16 = 16; // Future extensions go here. } // The shape of the tensor. repeated int64 dims = 1; // The data type of the tensor. // This field MUST have a valid TensorProto.DataType value int32 data_type = 2; // For very large tensors, we may want to store them in chunks, in which // case the following fields will specify the segment that is stored in // the current TensorProto. message Segment { int64 begin = 1; int64 end = 2; } Segment segment = 3; // Tensor content must be organized in row-major order. // // Depending on the data_type field, exactly one of the fields below with // name ending in _data is used to store the elements of the tensor. // For float and complex64 values // Complex64 tensors are encoded as a single array of floats, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be FLOAT or COMPLEX64. repeated float float_data = 4 [packed = true]; // For int32, uint8, int8, uint16, int16, bool, and float16 values // float16 values must be bit-wise converted to an uint16_t prior // to writing to the buffer. // When this field is present, the data_type field MUST be // INT32, INT16, INT8, UINT16, UINT8, BOOL, or FLOAT16 repeated int32 int32_data = 5 [packed = true]; // For strings. // Each element of string_data is a UTF-8 encoded Unicode // string. No trailing null, no leading BOM. The protobuf "string" // scalar type is not used to match ML community conventions. // When this field is present, the data_type field MUST be STRING repeated bytes string_data = 6; // For int64. // When this field is present, the data_type field MUST be INT64 repeated int64 int64_data = 7 [packed = true]; // Optionally, a name for the tensor. string name = 8; // namespace Value // A human-readable documentation for this tensor. Markdown is allowed. string doc_string = 12; // Serializations can either use one of the fields above, or use this // raw bytes field. The only exception is the string case, where one is // required to store the content in the repeated bytes string_data field. // // When this raw_data field is used to store tensor value, elements MUST // be stored in as fixed-width, little-endian order. // Floating-point data types MUST be stored in IEEE 754 format. // Complex64 elements must be written as two consecutive FLOAT values, real component first. // Complex128 elements must be written as two consecutive DOUBLE values, real component first. // Boolean type MUST be written one byte per tensor element (00000001 for true, 00000000 for false). // // Note: the advantage of specific field rather than the raw_data field is // that in some cases (e.g. int data), protobuf does a better packing via // variable length storage, and may lead to smaller binary footprint. // When this field is present, the data_type field MUST NOT be STRING or UNDEFINED bytes raw_data = 9; // Data can be stored inside the protobuf file using type-specific fields or raw_data. // Alternatively, raw bytes data can be stored in an external file, using the external_data field. // external_data stores key-value pairs describing data location. Recognized keys are: // - "location" (required) - POSIX filesystem path relative to the directory where the ONNX // protobuf model was stored // - "offset" (optional) - position of byte at which stored data begins. Integer stored as string. // Offset values SHOULD be multiples 4096 (page size) to enable mmap support. // - "length" (optional) - number of bytes containing data. Integer stored as string. // - "checksum" (optional) - SHA1 digest of file specified in under 'location' key. repeated StringStringEntryProto external_data = 13; // Location of the data for this tensor. MUST be one of: // - DEFAULT - data stored inside the protobuf message. Data is stored in raw_data (if set) otherwise in type-specified field. // - EXTERNAL - data stored in an external location as described by external_data field. enum DataLocation { DEFAULT = 0; EXTERNAL = 1; } // If value not set, data is stored in raw_data (if set) otherwise in type-specified field. DataLocation data_location = 14; // For double // Complex128 tensors are encoded as a single array of doubles, // with the real components appearing in odd numbered positions, // and the corresponding imaginary component appearing in the // subsequent even numbered position. (e.g., [1.0 + 2.0i, 3.0 + 4.0i] // is encoded as [1.0, 2.0 ,3.0 ,4.0] // When this field is present, the data_type field MUST be DOUBLE or COMPLEX128 repeated double double_data = 10 [packed = true]; // For uint64 and uint32 values // When this field is present, the data_type field MUST be // UINT32 or UINT64 repeated uint64 uint64_data = 11 [packed = true]; } // A serialized sparse-tensor value message SparseTensorProto { // The sequence of non-default values are encoded as a tensor of shape [NNZ]. // The default-value is zero for numeric tensors, and empty-string for string tensors. TensorProto values = 1; // The indices of the non-default values, which may be stored in one of two formats. // (a) Indices can be a tensor of shape [NNZ, rank] with the [i,j]-th value // corresponding to the j-th index of the i-th value (in the values tensor). // (b) Indices can be a tensor of shape [NNZ], in which case the i-th value // must be the linearized-index of the i-th value (in the values tensor). // The linearized-index can be converted into an index tuple (k_1,...,k_rank) // using the shape provided below. // The indices must appear in ascending order without duplication. // In the first format, the ordering is lexicographic-ordering: // e.g., index-value [1,4] must appear before [2,1] TensorProto indices = 2; // The shape of the underlying dense-tensor: [dim_1, dim_2, ... dim_rank] repeated int64 dims = 3; } // Defines a tensor shape. A dimension can be either an integer value // or a symbolic variable. A symbolic variable represents an unknown // dimension. message TensorShapeProto { message Dimension { oneof value { int64 dim_value = 1; string dim_param = 2; // namespace Shape }; // Standard denotation can optionally be used to denote tensor // dimensions with standard semantic descriptions to ensure // that operations are applied to the correct axis of a tensor. // Refer to https://github.com/onnx/onnx/blob/master/docs/DimensionDenotation.md#denotation-definition // for pre-defined dimension denotations. string denotation = 3; }; repeated Dimension dim = 1; } // Types // // The standard ONNX data types. message TypeProto { message Tensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. int32 elem_type = 1; TensorShapeProto shape = 2; } // repeated T message Sequence { // The type and optional shape of each element of the sequence. // This field MUST be present for this version of the IR. TypeProto elem_type = 1; }; // map message Map { // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. // This field MUST refer to an integral type ([U]INT{8|16|32|64}) or STRING int32 key_type = 1; // This field MUST be present for this version of the IR. TypeProto value_type = 2; }; message SparseTensor { // This field MUST NOT have the value of UNDEFINED // This field MUST have a valid TensorProto.DataType value // This field MUST be present for this version of the IR. int32 elem_type = 1; TensorShapeProto shape = 2; } message Opaque { // When missing, the domain is the same as the model's. string domain = 1; // The name is optional but significant when provided. string name = 2; // parameters that help defining the type // DEPRECATED do not use. // repeated TypeProto parameters = 3; } oneof value { // The type of a tensor. Tensor tensor_type = 1; // NOTE: DNN-only implementations of ONNX MAY elect to not support non-tensor values // as input and output to graphs and nodes. These types are needed to naturally // support classical ML operators. DNN operators SHOULD restrict their input // and output types to tensors. // The type of a sequence. Sequence sequence_type = 4; // The type of a map. Map map_type = 5; SparseTensor sparse_tensor_type = 8; Opaque opaque_type = 7; } // An optional denotation can be used to denote the whole // type with a standard semantic description as to what is // stored inside. Refer to https://github.com/onnx/onnx/blob/master/docs/TypeDenotation.md#type-denotation-definition // for pre-defined type denotations. string denotation = 6; } // Operator Sets // // OperatorSets are uniquely identified by a (domain, opset_version) pair. message OperatorSetIdProto { // The domain of the operator set being identified. // The empty string ("") or absence of this field implies the operator // set that is defined as part of the ONNX specification. // This field MUST be present in this version of the IR when referring to any other operator set. string domain = 1; // The version of the operator set being identified. // This field MUST be present in this version of the IR. int64 version = 2; } // For using protobuf-lite option optimize_for = LITE_RUNTIME; onnx-1.7.0/onnx/defs/0000775000000000000000000000000013655345213013113 5ustar rootrootonnx-1.7.0/onnx/defs/function.h0000664000000000000000000000547013655345213015117 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include #include #include #include #include "attr_proto_util.h" #include "onnx/common/constants.h" #include "onnx/common/status.h" #include "onnx/onnx-operators_pb.h" #include "tensor_proto_util.h" namespace ONNX_NAMESPACE { // Helper function to expand a function node given the function proto void FunctionExpandHelper( const NodeProto& node, const FunctionProto& func, GraphProto& g, const std::string& node_prefix = ""); class FunctionBodyHelper { public: struct AttributeProtoWrapper { AttributeProto proto; AttributeProtoWrapper() {} AttributeProtoWrapper(const AttributeProto& attr_prot) { proto = attr_prot; } template AttributeProtoWrapper(const std::string& attr_name, T value) { proto = MakeAttribute(attr_name, value); } }; struct NodeDef { NodeDef( const std::vector& outputs, const std::string& op_type, const std::vector& inputs) : outputs(outputs), op_type(op_type), inputs(inputs) {} NodeDef( const std::vector& outputs, const std::string& op_type, const std::vector& inputs, const std::vector& attributes) : outputs(outputs), op_type(op_type), inputs(inputs), attributes(attributes) {} std::vector outputs; std::string op_type; std::vector inputs; std::vector attributes; }; /* BuildNodes() is an utility function for easily define a Function Body. To build a simple node: {{"Z"}, "Add", {"X", "Y"}} represents Z = Add(X,Y) To build a node with attribute: {{"Y"}, "Concat", {"X1", "X2", "X3"}, {{"axis", 1}}} represents Y = Concat(X1,X2,X3) with axis = 1 The attribute type are infered from the attribute value's c++ type Supported value types are int64_t -> int, vector -> ints float -> float, vector -> floats string -> string, vector ->strings For refering an attribute from parent, use: {MakeRefAttribute("axes", AttributeProto::INTS)}} For more examples, please find the references of this function */ static std::vector BuildNodes( const std::vector& node_defs); template static NodeDef Const(const std::string& name, const T& value) { return NodeDef{{name}, "Constant", {}, {{"value", ToTensor(value)}}}; } template static NodeDef Const(const std::string& name, const std::vector& values) { return NodeDef{{name}, "Constant", {}, {{"value", ToTensor(values)}}}; } }; } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/operator_sets_training.h0000664000000000000000000000102113655345213020042 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { // Declare training operators. // Iterate over schema from ai.onnx.training version 1 class OpSet_OnnxTraining_ver1 { public: static void ForEachSchema(std::function /* fn */) { } }; // Register training operators. inline void RegisterOnnxTrainingOperatorSetSchema() { RegisterOpSetSchema(); } } // namespace ONNX_NAMESPACEonnx-1.7.0/onnx/defs/reduction/0000775000000000000000000000000013655345213015107 5ustar rootrootonnx-1.7.0/onnx/defs/reduction/defs.cc0000664000000000000000000001763113655345213016347 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { std::vector GetSupportedDataTypesForReductionOps( bool supports8bit) { if (supports8bit) { auto data_types = OpSchema::numeric_types_for_math_reduction(); data_types.push_back("tensor(uint8)"); data_types.push_back("tensor(int8)"); return data_types; } return OpSchema::numeric_types_for_math_reduction(); } std::function ReduceDocGenerator( const char* name, bool supports_8bit_datatypes = false) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Computes the {name} of the input tensor's element along the provided axes. The resulted tensor has the same rank as the input if keepdims equal 1. If keepdims equal 0, then the resulted tensor have the reduced dimension pruned. The above behavior is similar to numpy, with the exception that numpy default keepdims to False instead of True.)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc.c_str()); schema.Attr( "axes", "A list of integers, along which to reduce. The default is to reduce over " "all the dimensions of the input tensor. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "keepdims", "Keep the reduced dimension or not, default 1 mean keep reduced dimension.", AttributeProto::INT, static_cast(1)); schema.Input(0, "data", "An input tensor.", "T"); schema.Output(0, "reduced", "Reduced output tensor.", "T"); schema.TypeConstraint( "T", GetSupportedDataTypesForReductionOps(supports_8bit_datatypes), supports_8bit_datatypes ? "Constrain input and output types to high-precision and 8 bit numeric tensors." : "Constrain input and output types to high-precision numeric tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } int64_t keep_dims = 1; auto attr_proto = ctx.getAttribute("keepdims"); if (attr_proto) { keep_dims = attr_proto->i(); } auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); int64_t input_ndim = input_shape.dim_size(); auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); std::vector axes; auto axes_proto = ctx.getAttribute("axes"); if (axes_proto) axes.assign(axes_proto->ints().begin(), axes_proto->ints().end()); for (size_t i = 0; i < axes.size(); ++i) { if (axes[i] < -input_ndim || axes[i] >= input_ndim) { fail_shape_inference( "axis must be in [-rank, rank-1]. input rank was ", input_ndim); } if (axes[i] < 0) axes[i] += input_ndim; } // do we need handle negative axis? for (int i = 0; i < input_ndim; ++i) { // axes empty means reduce all dim if (!axes.empty() && std::find(axes.begin(), axes.end(), i) == axes.end()) { auto dim = output_shape->add_dim(); dim->CopyFrom(input_shape.dim(i)); } else { if (keep_dims == 1) { auto dim = output_shape->add_dim(); dim->set_dim_value(1); } } } }); }; } ONNX_OPERATOR_SET_SCHEMA( ReduceMax, 12, OpSchema().FillUsing(ReduceDocGenerator("max", true))); ONNX_OPERATOR_SET_SCHEMA( ReduceMin, 12, OpSchema().FillUsing(ReduceDocGenerator("min", true))); ONNX_OPERATOR_SET_SCHEMA( ReduceSum, 11, OpSchema().FillUsing(ReduceDocGenerator("sum"))); ONNX_OPERATOR_SET_SCHEMA( ReduceSumSquare, 11, OpSchema().FillUsing(ReduceDocGenerator("sum square"))); ONNX_OPERATOR_SET_SCHEMA( ReduceMean, 11, OpSchema().FillUsing(ReduceDocGenerator("mean"))); ONNX_OPERATOR_SET_SCHEMA( ReduceProd, 11, OpSchema().FillUsing(ReduceDocGenerator("product"))); ONNX_OPERATOR_SET_SCHEMA( ReduceLogSum, 11, OpSchema().FillUsing(ReduceDocGenerator("log sum"))); ONNX_OPERATOR_SET_SCHEMA( ReduceLogSumExp, 11, OpSchema().FillUsing(ReduceDocGenerator("log sum exponent"))); ONNX_OPERATOR_SET_SCHEMA( ReduceL1, 11, OpSchema().FillUsing(ReduceDocGenerator("L1 norm"))); ONNX_OPERATOR_SET_SCHEMA( ReduceL2, 11, OpSchema().FillUsing(ReduceDocGenerator("L2 norm"))); std::function ArgReduceDocGenerator(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Computes the indices of the {name} elements of the input tensor's element along the provided axis. The resulting tensor has the same rank as the input if keepdims equal 1. If keepdims equal 0, then the resulting tensor have the reduced dimension pruned. If select_last_index is True (default False), the index of the last occurrence of the {name} is selected if the {name} appears more than once in the input. Otherwise the index of the first occurrence is selected. The type of the output tensor is integer.)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc.c_str()); schema.Attr( "axis", "The axis in which to compute the arg indices. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INT, static_cast(0)); schema.Attr( "keepdims", "Keep the reduced dimension or not, default 1 mean keep reduced dimension.", AttributeProto::INT, static_cast(1)); schema.Attr( "select_last_index", "Whether to select the last index or the first index if the {name} appears in multiple indices, default is False (first index).", AttributeProto::INT, static_cast(0)); schema.Input(0, "data", "An input tensor.", "T"); schema.Output( 0, "reduced", "Reduced output tensor with integer data type.", "tensor(int64)"); schema.TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // set output element type to int64 updateOutputElemType(ctx, 0, TensorProto_DataType_INT64); if (!hasNInputShapes(ctx, 1)) { return; } auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); int64_t input_ndim = input_shape.dim_size(); int64_t axis = 0; // default to 0 auto axis_proto = ctx.getAttribute("axis"); if (axis_proto) { axis = axis_proto->i(); if (axis < -input_ndim || axis >= input_ndim) { fail_shape_inference( "'axis' must be in [-rank(indices), rank(indices)-1]"); } if (axis < 0) axis += input_ndim; } int64_t keep_dims = 1; auto attr_proto = ctx.getAttribute("keepdims"); if (attr_proto) { keep_dims = attr_proto->i(); } // do we need handle negative axis? for (int i = 0; i < input_ndim; ++i) { if (i != axis) { auto dim = output_shape->add_dim(); dim->CopyFrom(input_shape.dim(i)); } else { if (keep_dims == 1) { auto dim = output_shape->add_dim(); dim->set_dim_value(1); } } } }); }; } // namespace ONNX_NAMESPACE ONNX_OPERATOR_SET_SCHEMA( ArgMax, 12, OpSchema().FillUsing(ArgReduceDocGenerator("max"))); ONNX_OPERATOR_SET_SCHEMA( ArgMin, 12, OpSchema().FillUsing(ArgReduceDocGenerator("min"))); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/reduction/old.cc0000664000000000000000000002346413655345213016205 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { std::function ReduceDocGenerator_opset1( const char* name, int opset = 1) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Computes the {name} of the input tensor's element along the provided axes. The resulted tensor has the same rank as the input if keepdims equal 1. If keepdims equal 0, then the resulted tensor have the reduced dimension pruned. The above behavior is similar to numpy, with the exception that numpy default keepdims to False instead of True.)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc.c_str()); schema.Attr( "axes", opset >= 11 ? "A list of integers, along which to reduce. The default is to reduce over " "all the dimensions of the input tensor. Accepted range is [-r, r-1] where r = rank(data)." : "A list of integers, along which to reduce. The default is to reduce over " "all the dimensions of the input tensor.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "keepdims", "Keep the reduced dimension or not, default 1 mean keep reduced dimension.", AttributeProto::INT, static_cast(1)); schema.Input(0, "data", "An input tensor.", "T"); schema.Output(0, "reduced", "Reduced output tensor.", "T"); schema.TypeConstraint( "T", OpSchema::numeric_types_for_math_reduction(), "Constrain input and output types to high-precision numeric tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } int64_t keep_dims = 1; auto attr_proto = ctx.getAttribute("keepdims"); if (attr_proto) { keep_dims = attr_proto->i(); } auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); int64_t input_ndim = input_shape.dim_size(); auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); std::vector axes; auto axes_proto = ctx.getAttribute("axes"); if (axes_proto) axes.assign(axes_proto->ints().begin(), axes_proto->ints().end()); for (size_t i = 0; i < axes.size(); ++i) { if (axes[i] < 0) axes[i] += input_ndim; } // do we need handle negative axis? for (int i = 0; i < input_ndim; ++i) { // axes empty means reduce all dim if (!axes.empty() && std::find(axes.begin(), axes.end(), i) == axes.end()) { auto dim = output_shape->add_dim(); dim->CopyFrom(input_shape.dim(i)); } else { if (keep_dims == 1) { auto dim = output_shape->add_dim(); dim->set_dim_value(1); } } } }); }; } ONNX_OPERATOR_SET_SCHEMA( ReduceMax, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("max"))); ONNX_OPERATOR_SET_SCHEMA( ReduceMin, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("min"))); ONNX_OPERATOR_SET_SCHEMA( ReduceSum, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("sum"))); ONNX_OPERATOR_SET_SCHEMA( ReduceSumSquare, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("sum square"))); ONNX_OPERATOR_SET_SCHEMA( ReduceMean, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("mean"))); ONNX_OPERATOR_SET_SCHEMA( ReduceProd, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("product"))); ONNX_OPERATOR_SET_SCHEMA( ReduceLogSum, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("log sum"))); ONNX_OPERATOR_SET_SCHEMA( ReduceLogSumExp, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("log sum exponent"))); ONNX_OPERATOR_SET_SCHEMA( ReduceL1, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("L1 norm"))); ONNX_OPERATOR_SET_SCHEMA( ReduceL2, 1, OpSchema().FillUsing(ReduceDocGenerator_opset1("L2 norm"))); ONNX_OPERATOR_SET_SCHEMA( ReduceMax, 11, OpSchema().FillUsing(ReduceDocGenerator_opset1("max", 11))); ONNX_OPERATOR_SET_SCHEMA( ReduceMin, 11, OpSchema().FillUsing(ReduceDocGenerator_opset1("min", 11))); std::function ArgReduceDocGenerator_opset1(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Computes the indices of the {name} elements of the input tensor's element along the provided axis. The resulted tensor has the same rank as the input if keepdims equal 1. If keepdims equal 0, then the resulted tensor have the reduced dimension pruned. The type of the output tensor is integer.)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc.c_str()); schema.Attr( "axis", "The axis in which to compute the arg indices.", AttributeProto::INT, static_cast(0)); schema.Attr( "keepdims", "Keep the reduced dimension or not, default 1 mean keep reduced dimension.", AttributeProto::INT, static_cast(1)); schema.Input(0, "data", "An input tensor.", "T"); schema.Output( 0, "reduced", "Reduced output tensor with integer data type.", "tensor(int64)"); schema.TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // set output element type to int64 updateOutputElemType(ctx, 0, TensorProto_DataType_INT64); if (!hasNInputShapes(ctx, 1)) { return; } auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); int64_t input_ndim = input_shape.dim_size(); int64_t axis = 0; // default to 0 auto axis_proto = ctx.getAttribute("axis"); if (axis_proto) { axis = axis_proto->i(); if (axis < 0) axis += input_ndim; } int64_t keep_dims = 1; auto attr_proto = ctx.getAttribute("keepdims"); if (attr_proto) { keep_dims = attr_proto->i(); } // do we need handle negative axis? for (int i = 0; i < input_ndim; ++i) { if (i != axis) { auto dim = output_shape->add_dim(); dim->CopyFrom(input_shape.dim(i)); } else { if (keep_dims == 1) { auto dim = output_shape->add_dim(); dim->set_dim_value(1); } } } }); }; } // namespace ONNX_NAMESPACE ONNX_OPERATOR_SET_SCHEMA( ArgMax, 1, OpSchema().FillUsing(ArgReduceDocGenerator_opset1("max"))); ONNX_OPERATOR_SET_SCHEMA( ArgMin, 1, OpSchema().FillUsing(ArgReduceDocGenerator_opset1("min"))); std::function ArgReduceDocGenerator_opset11(const char* name) { return [=](OpSchema& schema) { std::string doc = R"DOC( Computes the indices of the {name} elements of the input tensor's element along the provided axis. The resulting tensor has the same rank as the input if keepdims equal 1. If keepdims equal 0, then the resulting tensor have the reduced dimension pruned. The type of the output tensor is integer.)DOC"; ReplaceAll(doc, "{name}", name); schema.SetDoc(doc.c_str()); schema.Attr( "axis", "The axis in which to compute the arg indices. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INT, static_cast(0)); schema.Attr( "keepdims", "Keep the reduced dimension or not, default 1 mean keep reduced dimension.", AttributeProto::INT, static_cast(1)); schema.Input(0, "data", "An input tensor.", "T"); schema.Output( 0, "reduced", "Reduced output tensor with integer data type.", "tensor(int64)"); schema.TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // set output element type to int64 updateOutputElemType(ctx, 0, TensorProto_DataType_INT64); if (!hasNInputShapes(ctx, 1)) { return; } auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); int64_t input_ndim = input_shape.dim_size(); int64_t axis = 0; // default to 0 auto axis_proto = ctx.getAttribute("axis"); if (axis_proto) { axis = axis_proto->i(); if (axis < -input_ndim || axis >= input_ndim) { fail_shape_inference( "'axis' must be in [-rank(indices), rank(indices)-1]"); } if (axis < 0) axis += input_ndim; } int64_t keep_dims = 1; auto attr_proto = ctx.getAttribute("keepdims"); if (attr_proto) { keep_dims = attr_proto->i(); } // do we need handle negative axis? for (int i = 0; i < input_ndim; ++i) { if (i != axis) { auto dim = output_shape->add_dim(); dim->CopyFrom(input_shape.dim(i)); } else { if (keep_dims == 1) { auto dim = output_shape->add_dim(); dim->set_dim_value(1); } } } }); }; } // namespace ONNX_NAMESPACE ONNX_OPERATOR_SET_SCHEMA( ArgMax, 11, OpSchema().FillUsing(ArgReduceDocGenerator_opset11("max"))); ONNX_OPERATOR_SET_SCHEMA( ArgMin, 11, OpSchema().FillUsing(ArgReduceDocGenerator_opset11("min"))); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/object_detection/0000775000000000000000000000000013655345213016417 5ustar rootrootonnx-1.7.0/onnx/defs/object_detection/old.cc0000664000000000000000000000674113655345213017514 0ustar rootroot// Copyright (c) Facebook Inc. and Microsoft Corporation. // Licensed under the MIT license. #include "onnx/defs/schema.h" using namespace ONNX_NAMESPACE; namespace ONNX_NAMESPACE { static const char* NonMaxSuppression_doc = R"DOC( Filter out boxes that have high intersection-over-union (IOU) overlap with previously selected boxes. Bounding boxes with score less than score_threshold are removed. Bounding box format is indicated by attribute center_point_box. Note that this algorithm is agnostic to where the origin is in the coordinate system and more generally is invariant to orthogonal transformations and translations of the coordinate system; thus translating or reflections of the coordinate system result in the same boxes being selected by the algorithm. The selected_indices output is a set of integers indexing into the input collection of bounding boxes representing the selected boxes. The bounding box coordinates corresponding to the selected indices can then be obtained using the Gather or GatherND operation. )DOC"; ONNX_OPERATOR_SET_SCHEMA( NonMaxSuppression, 10, OpSchema() .Input( 0, "boxes", "An input tensor with shape [num_batches, spatial_dimension, 4]. The single box data format is indicated by center_point_box.", "tensor(float)") .Input( 1, "scores", "An input tensor with shape [num_batches, num_classes, spatial_dimension]", "tensor(float)") .Input( 2, "max_output_boxes_per_class", "Integer representing the maximum number of boxes to be selected per batch per class. It is a scalar. Default to 0, which means no output.", "tensor(int64)", OpSchema::Optional) .Input( 3, "iou_threshold", "Float representing the threshold for deciding whether boxes overlap too much with respect to IOU. It is scalar. Value range [0, 1]. Default to 0.", "tensor(float)", OpSchema::Optional) .Input( 4, "score_threshold", "Float representing the threshold for deciding when to remove boxes based on score. It is a scalar.", "tensor(float)", OpSchema::Optional) .Output( 0, "selected_indices", "selected indices from the boxes tensor. [num_selected_indices, 3], the selected index format is [batch_index, class_index, box_index].", "tensor(int64)") .Attr( "center_point_box", "Integer indicate the format of the box data. The default is 0. " "0 - the box data is supplied as [y1, x1, y2, x2] where (y1, x1) and (y2, x2) are the coordinates of any diagonal pair of box corners " "and the coordinates can be provided as normalized (i.e., lying in the interval [0, 1]) or absolute. Mostly used for TF models. " "1 - the box data is supplied as [x_center, y_center, width, height]. Mostly used for Pytorch models.", AttributeProto::INT, static_cast(0)) .SetDoc(NonMaxSuppression_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto selected_indices_type = ctx.getOutputType(0)->mutable_tensor_type(); selected_indices_type->set_elem_type( ::ONNX_NAMESPACE::TensorProto_DataType:: TensorProto_DataType_INT64); })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/object_detection/defs.cc0000664000000000000000000002162113655345213017651 0ustar rootroot// Copyright (c) Facebook Inc. and Microsoft Corporation. // Licensed under the MIT license. #include "onnx/defs/schema.h" using namespace ONNX_NAMESPACE; namespace ONNX_NAMESPACE { static const char* RoiAlign_ver1_doc = R"DOC( Region of Interest (RoI) align operation described in the [Mask R-CNN paper](https://arxiv.org/abs/1703.06870). RoiAlign consumes an input tensor X and region of interests (rois) to apply pooling across each RoI; it produces a 4-D tensor of shape (num_rois, C, output_height, output_width). RoiAlign is proposed to avoid the misalignment by removing quantizations while converting from original image into feature map and from feature map into RoI feature; in each ROI bin, the value of the sampled locations are computed directly through bilinear interpolation. )DOC"; ONNX_OPERATOR_SET_SCHEMA( RoiAlign, 10, OpSchema() .SetDoc(RoiAlign_ver1_doc) .Attr( "spatial_scale", "Multiplicative spatial scale factor to translate ROI coordinates " "from their input spatial scale to the scale used when pooling, " "i.e., spatial scale of the input feature map X relative to the " "input image. E.g.; default is 1.0f. ", AttributeProto::FLOAT, 1.f) .Attr( "output_height", "default 1; Pooled output Y's height.", AttributeProto::INT, static_cast(1)) .Attr( "output_width", "default 1; Pooled output Y's width.", AttributeProto::INT, static_cast(1)) .Attr( "sampling_ratio", "Number of sampling points in the interpolation grid used to compute " "the output value of each pooled output bin. If > 0, then exactly " "sampling_ratio x sampling_ratio grid points are used. If == 0, then " "an adaptive number of grid points are used (computed as " "ceil(roi_width / output_width), and likewise for height). Default is 0.", AttributeProto::INT, static_cast(0)) .Attr( "mode", "The pooling method. Two modes are supported: 'avg' and 'max'. " "Default is 'avg'.", AttributeProto::STRING, std::string("avg")) .Input( 0, "X", "Input data tensor from the previous operator; " "4-D feature map of shape (N, C, H, W), " "where N is the batch size, C is the number of channels, " "and H and W are the height and the width of the data.", "T1") .Input( 1, "rois", "RoIs (Regions of Interest) to pool over; rois is " "2-D input of shape (num_rois, 4) given as " "[[x1, y1, x2, y2], ...]. " "The RoIs' coordinates are in the coordinate system of the input image. " "Each coordinate set has a 1:1 correspondence with the 'batch_indices' input.", "T1") .Input( 2, "batch_indices", "1-D tensor of shape (num_rois,) with each element denoting " "the index of the corresponding image in the batch.", "T2") .Output( 0, "Y", "RoI pooled output, 4-D tensor of shape " "(num_rois, C, output_height, output_width). The r-th batch element Y[r-1] " "is a pooled feature map corresponding to the r-th RoI X[r-1].", "T1") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain types to float tensors.") .TypeConstraint( "T2", {"tensor(int64)"}, "Constrain types to int tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); size_t input_param = 0, rois_param = 1, batch_index_param = 2; checkInputRank(ctx, input_param, 4); checkInputRank(ctx, rois_param, 2); checkInputRank(ctx, batch_index_param, 1); // Output dimensions, initialized to an unknown-dimension-value Dim num_rois, C, ht, width; // Get value of C from dim 1 of input_param, if available unifyInputDim(ctx, input_param, 1, C); // Get value of num_rois from dim 0 of rois_param, if available unifyInputDim(ctx, rois_param, 0, num_rois); // ... or from dim 0 of batch_index_param, if available unifyInputDim(ctx, batch_index_param, 0, num_rois); // Get height from attribute, using default-value of 1 unifyDim(ht, getAttribute(ctx, "output_height", 1)); // Get width from attribute, using default-value of 1 unifyDim(width, getAttribute(ctx, "output_width", 1)); // set output shape: updateOutputShape(ctx, 0, {num_rois, C, ht, width}); })); static const char* NonMaxSuppression_doc = R"DOC( Filter out boxes that have high intersection-over-union (IOU) overlap with previously selected boxes. Bounding boxes with score less than score_threshold are removed. Bounding box format is indicated by attribute center_point_box. Note that this algorithm is agnostic to where the origin is in the coordinate system and more generally is invariant to orthogonal transformations and translations of the coordinate system; thus translating or reflections of the coordinate system result in the same boxes being selected by the algorithm. The selected_indices output is a set of integers indexing into the input collection of bounding boxes representing the selected boxes. The bounding box coordinates corresponding to the selected indices can then be obtained using the Gather or GatherND operation. )DOC"; ONNX_OPERATOR_SET_SCHEMA( NonMaxSuppression, 11, OpSchema() .Input( 0, "boxes", "An input tensor with shape [num_batches, spatial_dimension, 4]. The single box data format is indicated by center_point_box.", "tensor(float)") .Input( 1, "scores", "An input tensor with shape [num_batches, num_classes, spatial_dimension]", "tensor(float)") .Input( 2, "max_output_boxes_per_class", "Integer representing the maximum number of boxes to be selected per batch per class. It is a scalar. Default to 0, which means no output.", "tensor(int64)", OpSchema::Optional) .Input( 3, "iou_threshold", "Float representing the threshold for deciding whether boxes overlap too much with respect to IOU. It is scalar. Value range [0, 1]. Default to 0.", "tensor(float)", OpSchema::Optional) .Input( 4, "score_threshold", "Float representing the threshold for deciding when to remove boxes based on score. It is a scalar.", "tensor(float)", OpSchema::Optional) .Output( 0, "selected_indices", "selected indices from the boxes tensor. [num_selected_indices, 3], the selected index format is [batch_index, class_index, box_index].", "tensor(int64)") .Attr( "center_point_box", "Integer indicate the format of the box data. The default is 0. " "0 - the box data is supplied as [y1, x1, y2, x2] where (y1, x1) and (y2, x2) are the coordinates of any diagonal pair of box corners " "and the coordinates can be provided as normalized (i.e., lying in the interval [0, 1]) or absolute. Mostly used for TF models. " "1 - the box data is supplied as [x_center, y_center, width, height]. Mostly used for Pytorch models.", AttributeProto::INT, static_cast(0)) .SetDoc(NonMaxSuppression_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference - Output is always of type INT64 auto* selected_indices_type = ctx.getOutputType(0)->mutable_tensor_type(); selected_indices_type->set_elem_type( TensorProto_DataType::TensorProto_DataType_INT64); // Shape inference // The exact shape cannot be determined as it depends on the input and // other input configurations for the op But part of the shape can be // established auto* selected_indices_shape = getOutputShape(ctx, 0); selected_indices_shape->clear_dim(); // Output is 2D always // The value of the first dim is determined by input data // hence its value cannot be determined statically selected_indices_shape->add_dim(); // The value of the second dim is 3 selected_indices_shape->add_dim()->set_dim_value(3); })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/attr_proto_util.h0000664000000000000000000000362613655345213016525 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/onnx-operators_pb.h" namespace ONNX_NAMESPACE { AttributeProto MakeAttribute(const std::string& attr_name, const float& value); AttributeProto MakeAttribute( const std::string& attr_name, const int64_t& value); AttributeProto MakeAttribute( const std::string& attr_name, const std::string& value); AttributeProto MakeAttribute( const std::string& attr_name, const TensorProto& value); AttributeProto MakeAttribute( const std::string& attr_name, const GraphProto& value); AttributeProto MakeAttribute( const std::string& attr_name, const std::vector& values); AttributeProto MakeAttribute( const std::string& attr_name, const std::vector& values); AttributeProto MakeAttribute( const std::string& attr_name, const std::vector& values); AttributeProto MakeAttribute( const std::string& attr_name, const std::vector& values); AttributeProto MakeAttribute( const std::string& attr_name, const std::vector& values); // Make a "reference" attribute for a node in a function body. // specifies the attribute name of both the function node and its // function body node. They're using the same attribute name. // specifies the attribute type. AttributeProto MakeRefAttribute( const std::string& attr_name, AttributeProto_AttributeType type); // Make a "reference" attribute for a node in a function body. // specifies the attribute name of the function body node. // specifies the referred attribute name of the function // node. // specifies the attribute type. AttributeProto MakeRefAttribute( const std::string& attr_name, const std::string& referred_attr_name, AttributeProto_AttributeType type); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/tensor/0000775000000000000000000000000013655345213014425 5ustar rootrootonnx-1.7.0/onnx/defs/tensor/utils.h0000664000000000000000000000173113655345213015740 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/defs/schema.h" #include "onnx/defs/tensor_proto_util.h" #include namespace ONNX_NAMESPACE { // The below is called by ops after opset 11, inclusively. void resizeShapeInference(InferenceContext& ctx, bool is_resize_op); void resizeShapeInferenceHelper( const TensorShapeProto& input_shape, const std::vector& scales_data, TensorShapeProto* output_shape); void resizeShapeInferenceHelper( const TensorShapeProto& input_shape, const std::vector& sizes_data, TensorShapeProto* output_shape); // The below is called by ops between opset 7 and opset 10, inclusively. void resizeShapeInference_opset7_to_10(InferenceContext& ctx); void resizeShapeInferenceHelper_opset7_to_10( const TensorShapeProto& input_shape, const std::vector& scales_data, TensorShapeProto* output_shape); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/tensor/old.cc0000664000000000000000000020131713655345213015516 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include #include "onnx/defs/tensor/utils.h" namespace ONNX_NAMESPACE { static const char* Cast_ver1_doc = R"DOC( The operator casts the elements of a given input tensor to a data type specified by the 'to' argument and returns an output tensor of the same size in the converted type. The 'to' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message. NOTE: Casting to and from strings is not supported yet. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Cast, 1, OpSchema() .SetDoc(Cast_ver1_doc) .Attr( "to", "The data type to which the elements of the input tensor are cast. " "Strictly must be one of the types from DataType enum in TensorProto", AttributeProto::STRING) .Input(0, "input", "Input tensor to be cast.", "T1") .Output( 0, "output", "Output tensor with the same shape as input with type " "specified by the 'to' argument", "T2") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain input types. Casting from strings and complex are not supported.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain output types. Casting to strings and complex are not supported.")); static const char* Cast_ver6_doc = R"DOC( The operator casts the elements of a given input tensor to a data type specified by the 'to' argument and returns an output tensor of the same size in the converted type. The 'to' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message. NOTE: Casting to and from strings is not supported yet. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Cast, 6, OpSchema() .SetDoc(Cast_ver6_doc) .Attr( "to", "The data type to which the elements of the input tensor are cast. " "Strictly must be one of the types from DataType enum in TensorProto", AttributeProto::INT) .Input(0, "input", "Input tensor to be cast.", "T1") .Output( 0, "output", "Output tensor with the same shape as input with type " "specified by the 'to' argument", "T2") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain input types. Casting from strings and complex are not supported.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain output types. Casting to strings and complex are not supported.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromAttributeToOutput(ctx, "to", 0); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* Concat_ver1_doc = R"DOC(Concatenate a list of tensors into a single tensor)DOC"; ONNX_OPERATOR_SET_SCHEMA( Concat, 1, OpSchema() .Attr( "axis", "Which axis to concat on. Default value is 1.", AttributeProto::INT, OPTIONAL_VALUE) .SetDoc(Concat_ver1_doc) .Input( 0, "inputs", "List of tensors for concatenation", "T", OpSchema::Variadic) .Output(0, "concat_result", "Concatenated tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain output types to float tensors.")); ONNX_OPERATOR_SET_SCHEMA( Concat, 4, OpSchema() .Attr("axis", "Which axis to concat on", AttributeProto::INT) .SetDoc("Concatenate a list of tensors into a single tensor") .Input( 0, "inputs", "List of tensors for concatenation", "T", OpSchema::Variadic) .Output(0, "concat_result", "Concatenated tensor", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); auto numInputs = ctx.getNumInputs(); if (numInputs < 1 || !hasNInputShapes(ctx, static_cast(numInputs))) { return; } auto rank = ctx.getInputType(0)->tensor_type().shape().dim_size(); auto axisAttr = ctx.getAttribute("axis"); if (!axisAttr) { fail_shape_inference("Required attribute axis is missing"); } int axis = static_cast(axisAttr->i()); if (rank <= axis) { fail_shape_inference("rank must be greater than axis"); } if (axis < 0) { return; // TODO: check if negative axis must be supported } bool all_lengths_known = true; int total_length = 0; auto* output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); for (int64_t i = 0; i < rank; ++i) { output_shape->add_dim(); } for (size_t i = 0; i < numInputs; i++) { const auto& shape = ctx.getInputType(i)->tensor_type().shape(); if (shape.dim_size() != rank) fail_shape_inference("All inputs to Concat must have same rank"); for (int j = 0; j < rank; j++) { if (j == axis) { if (shape.dim(j).has_dim_value()) { total_length += static_cast(shape.dim(j).dim_value()); } else { all_lengths_known = false; } } else { auto& output_dim = *output_shape->mutable_dim(j); const auto& input_dim = shape.dim(j); mergeInDimensionInfo(input_dim, output_dim, j); } } } if (all_lengths_known) { output_shape->mutable_dim(axis)->set_dim_value(total_length); } })); static const char* Split_ver1_doc = R"DOC(Split a tensor into a list of tensors, along the specified 'axis'. The lengths of the split can be specified using argument 'axis' or optional second input blob to the operator. Otherwise, the tensor is split to equal sized parts. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Split, 1, OpSchema() .Input(0, "input", "The tensor to split", "T") .Input( 1, "split", "Optional list of output lengths (see also arg 'split')", "T", OpSchema::Optional) .Output( 0, "outputs...", "One or more outputs forming list of tensors after splitting", "T", OpSchema::Variadic) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input types to float tensors.") .Attr("axis", "Which axis to split on", AttributeProto::INT, OPTIONAL_VALUE) .Attr("split", "length of each output", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Split_ver1_doc)); static const char* Pad_ver1_doc = R"DOC( Given `data` tensor, paddings, mode, and value. Example: Insert 0 paddings to the beginning of the second dimension. data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] paddings = [0, 0, 2, 0] output = [ [ [0.0, 0.0, 1.0, 1.2], [0.0, 0.0, 2.3, 3.4], [0.0, 0.0, 4.5, 5.7], ], ] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Pad, 1, OpSchema() .Attr( "paddings", "List of integers indicate the padding element count at the " "beginning and end of each axis, for 2D it is the number of pixel. " "`paddings` rank should be double of the input's rank. `paddings` format should be as follow " "[x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of pixels " "added at the beginning of axis `i` and xi_end, the number of pixels added at " "the end of axis `i`.", AttributeProto::INTS) .Attr( "mode", "Three modes: constant(default), reflect, edge", AttributeProto::STRING, std::string("constant")) .Attr( "value", "One float, indicates the value to be filled, default is 0", AttributeProto::FLOAT, 0.0f) .SetDoc(Pad_ver1_doc) .Input(0, "data", "Input tensor.", "T") .Output(0, "output", "Tensor after padding.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Reshape_ver1_doc = R"DOC( Reshape the input tensor similar to numpy.reshape. It takes a tensor as input and an argument `shape`. It outputs the reshaped tensor. At most one dimension of the new shape can be -1. In this case, the value is inferred from the size of the tensor and the remaining dimensions. A dimension could also be 0, in which case the actual dimension value is unchanged (i.e. taken from the input tensor).)DOC"; ONNX_OPERATOR_SET_SCHEMA( Reshape, 1, OpSchema() .SetDoc(Reshape_ver1_doc) .Attr("shape", "New shape", AttributeProto::INTS, OPTIONAL_VALUE) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .Input(0, "data", "An input tensor.", "T") .Output(0, "reshaped", "Reshaped data.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Upsample_ver1_doc = R"DOC( Upsample the input tensor. The width and height of the output tensor are: output_width = floor(input_width * width_scale), output_height = floor(input_height * height_scale). Example: Given `data` tensor, width_scale, height_scale, mode, Upsample the input 4-D tensor in nearest mode: data = [[[ [1, 2], [3, 4] ]]] width_scale = 2 height_scale = 2 mode = "nearest" output = [[[ [1, 1, 2, 2], [1, 1, 2, 2], [3, 3, 4, 4], [3, 3, 4, 4] ]]] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Tile, 1, OpSchema() .SetDoc("Repeat the elements of a tensor along an axis.") .Input(0, "input", "Input tensor of any shape.", "T") .Input( 1, "tiles", "Number of repeated copies to make of the input tensor.", "T") .Input(2, "axis", "Axis along which to repeat.", "T") .Output( 0, "output", "Output tensor of same shape and type as input.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input types to float tensors.") .TypeConstraint( "T1", {"tensor(int64)"}, "Constrain tiles and axis's type to int64 tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); // Only rank of output can be inferred. We can do better if second // input is a constant, but this requires extending InferenceContext // interface to get values of constant inputs. })); ONNX_OPERATOR_SET_SCHEMA( Upsample, 1, OpSchema() .SetSupportLevel(OpSchema::SupportType::EXPERIMENTAL) .Attr( "width_scale", "The scale along width dimension. It takes value greater than or equal to 1.", AttributeProto::FLOAT) .Attr( "height_scale", "The scale along height dimension. It takes value greater than or equal to 1.", AttributeProto::FLOAT) .Attr( "mode", "Two interpolation modes: nearest(default), bilinear", AttributeProto::STRING, std::string("nearest")) .Input(0, "X", "4-D tensor, [N,C,H,W]", "T") .Output(0, "Y", "4-D tensor after resizing, [N,C,H,W]", "T") .TypeConstraint( "T", {"tensor(bool)", "tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain output types to bool, int32, int64, float16, float, double tensors.") .SetDoc(Upsample_ver1_doc)); static const char* Upsample_ver7_doc = R"DOC( Upsample the input tensor. Each dimension value of the output tensor is: output_dimension = floor(input_dimension * scale). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Upsample, 7, OpSchema() .Attr( "scales", "The scale array along each dimension. It takes value greater than or equal to 1." " The number of elements of 'scales' should be the same as the rank of input 'X'.", AttributeProto::FLOATS) .Attr( "mode", "Two interpolation modes: nearest (default), and linear (including bilinear, trilinear, etc)", AttributeProto::STRING, std::string("nearest")) .Input(0, "X", "N-D tensor", "T") .Output(0, "Y", "N-D tensor after resizing", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .SetDoc(Upsample_ver7_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { if (!hasNInputShapes(ctx, 1)) { return; } propagateElemTypeFromInputToOutput(ctx, 0, 0); const auto& input_shape = getInputShape(ctx, 0); auto* output_shape = getOutputShape(ctx, 0); const auto* scales = ctx.getAttribute("scales"); if (output_shape->dim_size() > 0) { if (output_shape->dim_size() != input_shape.dim_size()) { fail_shape_inference( "Ranks inferred (", input_shape.dim_size(), ") is not equal to the existing rank value (", output_shape->dim_size(), ")."); } } else { // Infer the rank of output anyway for (int i = 0; i < input_shape.dim_size(); ++i) { output_shape->add_dim(); } } if (nullptr != scales) { // Infer output shape's dimension value if 'scales' is known. if (scales->type() == AttributeProto_AttributeType_FLOATS) { const std::vector scales_data( scales->floats().begin(), scales->floats().end()); if (scales_data.size() != static_cast(input_shape.dim_size())) { fail_shape_inference( "Number of elements of attribute 'scales' must be same as rank of input 'X'"); } resizeShapeInferenceHelper_opset7_to_10( input_shape, scales_data, output_shape); } else { fail_shape_inference("Attribute 'scales' must have floats type."); } // scales->type() == float } else { fail_shape_inference("Attribute 'scales' is required."); } // nullptr != scales })); static const char* Upsample_ver9_doc = R"DOC( Upsample the input tensor. Each dimension value of the output tensor is: output_dimension = floor(input_dimension * scale). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Upsample, 9, OpSchema() .Attr( "mode", "Two interpolation modes: nearest (default), and linear (including bilinear, trilinear, etc)", AttributeProto::STRING, std::string("nearest")) .Input(0, "X", "N-D tensor", "T") .Input( 1, "scales", "The scale array along each dimension. It takes value greater than or equal to 1." " The number of elements of 'scales' should be the same as the rank of input 'X'.", "tensor(float)") .Output(0, "Y", "N-D tensor after resizing", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input 'X' and output 'Y' to all tensor types.") .SetDoc(Upsample_ver9_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { resizeShapeInference_opset7_to_10(ctx); })); static const char* Resize_ver10_doc = R"DOC( Resize the input tensor. Each dimension value of the output tensor is: output_dimension = floor(input_dimension * scale). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Resize, 10, OpSchema() .Attr( "mode", "Two interpolation modes: nearest (default), and linear (including bilinear, trilinear, etc)", AttributeProto::STRING, std::string("nearest")) .Input(0, "X", "N-D tensor", "T") .Input( 1, "scales", "The scale array along each dimension. It takes value greater than 0. If it's less than 1," " it's sampling down, otherwise, it's upsampling. The number of elements of 'scales' should" " be the same as the rank of input 'X'.", "tensor(float)") .Output(0, "Y", "N-D tensor after resizing", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input 'X' and output 'Y' to all tensor types.") .SetDoc(Resize_ver10_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { resizeShapeInference_opset7_to_10(ctx); })); static const char* Slice_ver1_doc = R"DOC( Produces a slice of the input tensor along multiple axes. Similar to numpy: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html Slices uses `axes`, `starts` and `ends` attributes to specify the start and end dimension for each axis in the list of axes, it uses this information to slice the input `data` tensor. If a negative value is passed for any of the start or end indices, it represent number of elements before the end of that dimension. If the value passed to start or end is larger than the `n` (the number of elements in this dimension), it represents `n`. For slicing to the end of a dimension with unknown size, it is recommended to pass in `INT_MAX`. If `axes` are omitted, they are set to `[0, ..., ndim-1]`. Example 1: data = [ [1, 2, 3, 4], [5, 6, 7, 8], ] axes = [0, 1] starts = [1, 0] ends = [2, 3] result = [ [5, 6, 7], ] Example 2: data = [ [1, 2, 3, 4], [5, 6, 7, 8], ] starts = [0, 1] ends = [-1, 1000] result = [ [2, 3, 4], ] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Slice, 1, OpSchema() .SetDoc(Slice_ver1_doc) .Input(0, "data", "Tensor of data to extract slices from.", "T") .Attr( "axes", "Axes that `starts` and `ends` apply to. " "It's optional. If not present, will be treated as " "[0, 1, ..., len(`starts`) - 1].", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "starts", "Starting indices of corresponding axis in `axes`", AttributeProto::INTS) .Attr( "ends", "Ending indices (exclusive) of corresponding axis in axes`", AttributeProto::INTS) .Output(0, "output", "Sliced data tensor.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } std::vector starts; std::vector ends; if (!getRepeatedAttribute(ctx, "starts", starts) || !getRepeatedAttribute(ctx, "ends", ends) || starts.size() != ends.size()) { fail_shape_inference( "Incorrect or missing attribute value for starts and ends"); ; } std::vector axes; if (!getRepeatedAttribute(ctx, "axes", axes)) { for (int i = 0; (size_t)i < starts.size(); ++i) { axes.push_back(i); } } else if (axes.size() != starts.size()) { fail_shape_inference("Attribute axes has incorrect length"); ; } else if (!std::is_sorted(axes.begin(), axes.end())) { // TODO support shape inference for unsorted axes return; } ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); for (size_t i = 0, j = 0; (int64_t)i < ctx.getInputType(0)->tensor_type().shape().dim_size(); ++i) { auto* newdim = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim(); if (j < axes.size() && static_cast(axes[j]) == i) { // There's a lot of potential behaviors. For now just // handle some simple cases. if (ctx.getInputType(0) ->tensor_type() .shape() .dim((int)i) .has_dim_value() && starts[j] >= 0 && ends[j] >= 0) { auto newval = std::min( (int64_t)ctx.getInputType(0) ->tensor_type() .shape() .dim((int)i) .dim_value(), ends[j]) - starts[j]; if (newval >= 0) { newdim->set_dim_value(newval); } } ++j; } else { *newdim = ctx.getInputType(0)->tensor_type().shape().dim((int)i); } } })); static const char* Slice_ver10_doc = R"DOC( Produces a slice of the input tensor along multiple axes. Similar to numpy: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html Slices uses `starts`, `ends`, `axes` and `steps` inputs to specify the start and end dimension and step for each axis in the list of axes, it uses this information to slice the input `data` tensor. If a negative value is passed for any of the start or end indices, it represent number of elements before the end of that dimension. If the value passed to start or end is larger than the `n` (the number of elements in this dimension), it represents `n`. For slicing to the end of a dimension with unknown size, it is recommended to pass in `INT_MAX`. If a negative value is passed for step, it represents slicing backward. If `axes` are omitted, they are set to `[0, ..., ndim-1]`. If `steps` are omitted, they are set to `[1, ..., 1]` of length `len(starts)` Example 1: data = [ [1, 2, 3, 4], [5, 6, 7, 8], ] axes = [0, 1] starts = [1, 0] ends = [2, 3] steps = [1, 2] result = [ [5, 7], ] Example 2: data = [ [1, 2, 3, 4], [5, 6, 7, 8], ] starts = [0, 1] ends = [-1, 1000] result = [ [2, 3, 4], ] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Slice, 10, OpSchema() .SetDoc(Slice_ver10_doc) .Input(0, "data", "Tensor of data to extract slices from.", "T") .Input( 1, "starts", "1-D tensor of starting indices of corresponding axis in `axes`", "Tind") .Input( 2, "ends", "1-D tensor of ending indices (exclusive) of corresponding axis in `axes`", "Tind") .Input( 3, "axes", "1-D tensor of axes that `starts` and `ends` apply to.", "Tind", OpSchema::Optional) .Input( 4, "steps", "1-D tensor of slice step of corresponding axis in `axes`. Default to 1. ", "Tind", OpSchema::Optional) .Output(0, "output", "Sliced data tensor.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { size_t num_inputs = ctx.getNumInputs(); if (num_inputs != 3 && num_inputs != 4 && num_inputs != 5) { fail_type_inference( "Slice op must have either three, four or five inputs."); } propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } // Shape Inference if // 1. 2nd and 3rd input data (starts, ends) are available. // and 2. 4th and 5th optional input (axes, steps) are either not set, // or set and is initializer. const TensorProto* startsInitializer = ctx.getInputData(1); const TensorProto* endsInitializer = ctx.getInputData(2); const TensorProto* axesInitializer = hasInputShape(ctx, 3) ? ctx.getInputData(3) : nullptr; const TensorProto* stepsInitializer = hasInputShape(ctx, 4) ? ctx.getInputData(4) : nullptr; if (!startsInitializer || !endsInitializer || (hasInputShape(ctx, 3) && !ctx.getInputData(3)) || (hasInputShape(ctx, 4) && !ctx.getInputData(4))) { return; } // don't know data_type- can't proceed if (!startsInitializer->has_data_type()) return; auto get_initializer_data = [](const TensorProto* initializer) -> std::vector { std::vector vec; if (initializer->data_type() == TensorProto::INT64) { const auto& data = ParseData(initializer); vec.insert(vec.end(), data.begin(), data.end()); } else if (initializer->data_type() == TensorProto::INT32) { const auto& data = ParseData(initializer); vec.insert(vec.end(), data.begin(), data.end()); } else { // unaccepted data type fail_shape_inference( "Only supports `int32_t` or `int64_t` inputs for starts/ends/axes/steps"); } return vec; }; auto clamp = [](int64_t val, int64_t low, int64_t high) -> int64_t { if (val < low) return low; if (val > high) return high; return val; }; std::vector starts = get_initializer_data(startsInitializer); std::vector ends = get_initializer_data(endsInitializer); if (starts.size() != ends.size()) { fail_shape_inference( "Incorrect or missing input value for starts and ends"); } const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_rank = input_shape.dim_size(); std::vector axes(starts.size()); if (!axesInitializer) { std::iota(axes.begin(), axes.end(), 0); } else { axes = get_initializer_data(axesInitializer); if (axes.size() != starts.size()) { fail_shape_inference("Input axes has incorrect length"); } } std::vector steps; if (!stepsInitializer) { steps = std::vector(starts.size(), 1); } else { steps = get_initializer_data(stepsInitializer); if (steps.size() != axes.size()) { fail_shape_inference("Input steps has incorrect length"); } } for (size_t i = 0; (int64_t)i < input_rank; ++i) { // first update rank of output dim auto* output_dim = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim(); const auto& input_dim = input_shape.dim((int)i); if (input_dim.has_dim_value()) { output_dim->set_dim_value(input_dim.dim_value()); } else if (input_dim.has_dim_param()) { output_dim->set_dim_param(input_dim.dim_param()); } } std::unordered_set unique_axes; size_t axes_size = axes.size(); for (size_t axis_index = 0; axis_index < axes_size; ++axis_index) { auto axis = axes[axis_index] < 0 ? axes[axis_index] + static_cast(input_rank) : axes[axis_index]; if (axis >= static_cast(input_rank) || axis < 0) fail_shape_inference("Input axes has invalid data"); if (unique_axes.find(axis) != unique_axes.end()) fail_shape_inference("'axes' has duplicates"); unique_axes.insert(axis); auto input_dim = ctx.getInputType(0)->tensor_type().shape().dim((int)axis); // input dim value is missing - cannot perform shape inference for // this axis if (!input_dim.has_dim_value()) continue; const auto input_dim_value = input_dim.dim_value(); // process step auto step = steps[axis_index]; if (step == 0) fail_shape_inference("'step' cannot be 0"); // process start auto start = starts[axis_index]; if (start < 0) start += input_dim_value; if (step < 0) start = clamp(start, 0, input_dim_value - 1); else start = clamp(start, 0, input_dim_value); // process end auto end = ends[axis_index]; if (end < 0) end += input_dim_value; if (step < 0) end = clamp(end, -1, input_dim_value); else end = clamp(end, 0, input_dim_value); // find output dim value for this axis auto temp = static_cast(ceil(1.0 * (end - start) / step)); if (temp < 0) temp = 0; // assign output value ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->mutable_dim((int)axis) ->set_dim_value(temp); } })); static const char* Scatter_ver9_doc = R"DOC( Given `data`, `updates` and `indices` input tensors of rank r >= 1, write the values provided by `updates` into the first input, `data`, along `axis` dimension of `data` (by default outer-most one as axis=0) at corresponding `indices`. For each entry in `updates`, the target index in `data` is specified by corresponding entry in `indices` for dimension = axis, and index in source for dimension != axis. For instance, in a 2-D tensor case, data[indices[i][j]][j] = updates[i][j] if axis = 0, or data[i][indices[i][j]] = updates[i][j] if axis = 1, where i and j are loop counters from 0 up to the respective size in `updates` - 1. Example 1: data = [ [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], ] indices = [ [1, 0, 2], [0, 2, 1], ] updates = [ [1.0, 1.1, 1.2], [2.0, 2.1, 2.2], ] output = [ [2.0, 1.1, 0.0] [1.0, 0.0, 2.2] [0.0, 2.1, 1.2] ] Example 2: data = [[1.0, 2.0, 3.0, 4.0, 5.0]] indices = [[1, 3]] updates = [[1.1, 2.1]] axis = 1 output = [[1.0, 1.1, 3.0, 2.1, 5.0]] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Scatter, 9, OpSchema() .SetDoc(Scatter_ver9_doc) .Attr( "axis", "Which axis to scatter on. Negative value means " "counting dimensions from the back. Accepted range is [-r, r-1]", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of int32/int64 indices, of r >= 1 (same rank as input).", "Tind") .Input( 2, "updates", "Tensor of rank r >=1 (same rank and shape as indices)", "T") .Output(0, "output", "Tensor of rank r >= 1 (same rank as input).", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input and output types can be of any tensor type.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* DepthToSpace_ver1_doc = R"DOC(DepthToSpace rearranges (permutes) data from depth into blocks of spatial data. This is the reverse transformation of SpaceToDepth. More specifically, this op outputs a copy of the input tensor where values from the depth dimension are moved in spatial blocks to the height and width dimensions. )DOC"; ONNX_OPERATOR_SET_SCHEMA( DepthToSpace, 1, OpSchema() .Attr( "blocksize", "Blocks of [blocksize, blocksize] are moved.", AttributeProto::INT) .SetDoc(DepthToSpace_ver1_doc) .Input( 0, "input", "Input tensor of [N,C,H,W], where N is the batch axis, C is the channel or depth" ", H is the height and W is the width.", "T") .Output( 0, "output", "Output tensor of [N, C/(blocksize * blocksize), H * blocksize, W * blocksize].", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); auto blocksize = getAttribute(ctx, "blocksize", 0); if (blocksize <= 0) fail_shape_inference("Blocksize must be positive"); if (hasInputShape(ctx, 0)) { auto& input_shape = getInputShape(ctx, 0); if (input_shape.dim_size() == 4) { // TODO: Clarify what behavior should be if C is not a multiple of // blocksize*blocksize. updateOutputShape( ctx, 0, {input_shape.dim(0), input_shape.dim(1) / (blocksize * blocksize), input_shape.dim(2) * blocksize, input_shape.dim(3) * blocksize}); } else fail_shape_inference("Input tensor must be 4-dimensional"); } })); static const char* Gather_ver1_doc = R"DOC( Given `data` tensor of rank r >= 1, and `indices` tensor of rank q, gather entries of the axis dimension of `data` (by default outer-most one as axis=0) indexed by `indices`, and concatenates them in an output tensor of rank q + (r - 1). Example 1: ``` data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] indices = [ [0, 1], [1, 2], ] output = [ [ [1.0, 1.2], [2.3, 3.4], ], [ [2.3, 3.4], [4.5, 5.7], ], ] ``` Example 2: ``` data = [ [1.0, 1.2, 1.9], [2.3, 3.4, 3.9], [4.5, 5.7, 5.9], ] indices = [ [0, 2], ] axis = 1, output = [ [ [1.0, 1.9], [2.3, 3.9], [4.5, 5.9], ], ] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gather, 1, OpSchema() .SetDoc(Gather_ver1_doc) .Attr( "axis", "Which axis to gather on. Negative value means " "counting dimensions from the back. Accepted range is [-r, r-1]", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of int32/int64 indices, of any rank q. All index values are expected to be within bounds. " "It is an error if any of the index values are out of bounds.", "Tind") .Output(0, "output", "Tensor of rank q + (r - 1).", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to any tensor type.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 2)) { return; } const TensorShapeProto& data_shape = ctx.getInputType(0)->tensor_type().shape(); const TensorShapeProto& indices_shape = ctx.getInputType(1)->tensor_type().shape(); int r = data_shape.dim_size(); if (r < 1) { fail_shape_inference("data tensor must have rank >= 1"); } int q = indices_shape.dim_size(); int axis = static_cast(getAttribute(ctx, "axis", 0)); if (axis < -r || axis >= r) { fail_shape_inference("axis must be in [-r, r-1]"); } if (axis < 0) { axis += r; } int out_rank = q + r - 1; if (out_rank == 0) { ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); } for (int i = 0; i < out_rank; ++i) { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = (i < axis) ? data_shape.dim(i) : // i < axis < r (i >= axis && i < axis + q) ? indices_shape.dim(i - axis) : // i - axis < q data_shape.dim(i - q + 1); // i < out_rank < q + r - 1 } })); static const char* Squeeze_ver1_doc = R"DOC( Remove single-dimensional entries from the shape of a tensor. Takes a parameter `axes` with a list of axes to squeeze. If `axes` is not provided, all the single dimensions will be removed from the shape. If an axis is selected with shape entry not equal to one, an error is raised. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Squeeze, 1, OpSchema() .Attr( "axes", "List of non-negative integers, indicate the dimensions to squeeze.", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Squeeze_ver1_doc) .Input(0, "data", "Tensors with at least max(dims) dimensions.", "T") .Output(0, "squeezed", "Reshaped tensor with same data as input.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } std::vector axes; if (!getRepeatedAttribute(ctx, "axes", axes)) { return; } if (!ctx.getInputType(0)->tensor_type().has_shape()) { return; } ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); for (int i = 0, j = 0; i < input_shape.dim_size(); ++i) { if (static_cast(j) < axes.size() && axes[j] == i) { if (input_shape.dim(i).has_dim_value() && input_shape.dim(i).dim_value() != 1) { fail_shape_inference( "Dimension of input ", i, " must be 1 instead of ", input_shape.dim(i).dim_value()); } ++j; } else { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = input_shape.dim(i); } } })); static const char* Unsqueeze_ver1_doc = R"DOC( Insert single-dimensional entries to the shape of a tensor. Takes one required argument `axes`, a list of dimensions that will be inserted. Dimension indices in `axes` are as seen in the output tensor. For example: Given a tensor such that tensor with shape [3, 4, 5], then Unsqueeze(tensor, axes=[0, 4]) has shape [1, 3, 4, 5, 1] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Unsqueeze, 1, OpSchema() .Attr( "axes", "List of non-negative integers, indicate the dimensions to be inserted", AttributeProto::INTS) .SetDoc(Unsqueeze_ver1_doc) .Input(0, "data", "Original tensor", "T") .Output(0, "expanded", "Reshaped tensor with same data as input.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } std::vector axes; if (!getRepeatedAttribute(ctx, "axes", axes)) { return; } std::sort(axes.begin(), axes.end()); if (!ctx.getInputType(0)->tensor_type().has_shape()) { return; } ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); int j = 0; for (int i = 0; i < ctx.getInputType(0)->tensor_type().shape().dim_size(); ++i) { while (static_cast(j) < axes.size() && axes[j] == ctx.getOutputType(0)->tensor_type().shape().dim_size()) { ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() ->set_dim_value(1); ++j; } *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = ctx.getInputType(0)->tensor_type().shape().dim(i); } while (static_cast(j) < axes.size() && axes[j] == ctx.getOutputType(0)->tensor_type().shape().dim_size()) { ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() ->set_dim_value(1); ++j; } })); static const char* OneHot_ver9_doc = R"DOC( Produces a one-hot tensor based on inputs. The locations represented by the index values in the 'indices' input tensor will have 'on_value' and the other locations will have 'off_value' in the output tensor, where 'on_value' and 'off_value' are specified as part of required input argument 'values', which is a two-element tensor of format [off_value, on_value]. The rank of the output tensor will be one greater than the rank of the input tensor. The additional dimension is for one-hot representation. The additional dimension will be inserted at the position specified by 'axis'. If 'axis' is not specified then then additional dimension will be inserted as the innermost dimension, i.e. axis=-1. The size of the additional dimension is specified by required scalar input 'depth'. The type of the output tensor is the same as the type of the 'values' input. Any entries in the 'indices' input tensor with values outside the range [0, depth) will result in one-hot representation with all 'off_value' values in the output tensor. )DOC"; ONNX_OPERATOR_SET_SCHEMA( OneHot, 9, OpSchema() .SetDoc(OneHot_ver9_doc) .Attr( "axis", "(Optional) Axis along which one-hot representation in added. Default: axis=-1. " "axis=-1 means that the additional dimension will be inserted as the " "innermost/last dimension in the output tensor.", AttributeProto::INT, static_cast(-1)) .Input( 0, "indices", "Input tensor containing indices. The values must be non-negative integers. " "Any entries in the 'indices' input tensor with values outside the range [0, depth) " "will result in one-hot representation with all 'off_value' values in the output tensor." "In case 'indices' is of non-integer type, the values will be casted to int64 before use.", "T1") .Input( 1, "depth", "Scalar specifying the number of classes in one-hot tensor. This is also the size " "of the one-hot dimension (specified by 'axis' attribute) added on in the output " "tensor. The values in the 'indices' input tensor are expected to be " "in the range [0, depth). " "In case 'depth' is of non-integer type, it will be casted to int64 before use.", "T2") .Input( 2, "values", "Rank 1 tensor containing exactly two elements, in the format [off_value, on_value], " "where 'on_value' is the value used for filling locations specified in 'indices' input " "tensor, and 'off_value' is the value used for filling locations other than those specified " "in 'indices' input tensor. ", "T3") .Output( 0, "output", "Tensor of rank one greater than input tensor 'indices', i.e. rank(output) = rank(indices) + 1. " "The data type for the elements of the output tensor is the same as the type of input 'values' " "is used.", "T3") .TypeConstraint( "T1", OpSchema::all_numeric_types(), "Constrains input to only numeric types.") .TypeConstraint( "T2", OpSchema::all_numeric_types(), "Constrains input to only numeric types.") .TypeConstraint( "T3", OpSchema::all_tensor_types(), "Constrain to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Check that the node has three inputs. if (ctx.getNumInputs() != 3) { fail_type_inference("OneHot node must have three inputs."); } // Input 'depth' must be a scalar or a single-element vector. // TODO: Ideally to match spec for this input only allow Scalar should // be allowed. Making this change now can affect backward // compatibility for this op. Since this does not seem like a good // justification to update version for this op, allowing both scalar // and 1 element vector for now. In future when version update for // this op is done we should only allow scalar or chage the spec to // allow both. if (hasInputShape(ctx, 1)) { auto& depth_shape = getInputShape(ctx, 1); if (depth_shape.dim_size() != 0 && depth_shape.dim_size() != 1) { fail_type_inference( "Input 'depth' must be a scalar or rank 1 tensor."); } if (depth_shape.dim_size() == 1 && depth_shape.dim((int)0).has_dim_value() && depth_shape.dim((int)0).dim_value() != 1) { fail_type_inference( "Input 'depth' must have exactly one element."); } } // Input 'values' must be a two-element vector. if (hasInputShape(ctx, 2)) { auto& values_shape = getInputShape(ctx, 2); if (values_shape.dim_size() != 1) { fail_type_inference("Input 'values' must be rank 1 tensor."); } if (values_shape.dim((int)0).has_dim_value() && values_shape.dim((int)0).dim_value() != 2) { fail_type_inference( "Input 'values' must have exactly two elements."); } } // Set output type to be the same as the third input, 'values'. propagateElemTypeFromInputToOutput(ctx, 2, 0); // Set the output shape, if input 0 (indices) shape is available. if (hasInputShape(ctx, 0)) { const TensorShapeProto& indices_shape = ctx.getInputType(0)->tensor_type().shape(); int r = indices_shape.dim_size(); if (r < 1) { fail_shape_inference("Indices tensor must have rank >= 1"); } int out_rank = r + 1; int axis = static_cast(getAttribute(ctx, "axis", -1)); if (axis < -out_rank || axis >= out_rank) { fail_shape_inference( "'axis' must be in [-rank(indices)-1, rank(indices)]"); } if (axis < 0) { axis += out_rank; } auto* output_shape = getOutputShape(ctx, 0); for (int i = 0; i < out_rank; ++i) { auto* dim = output_shape->add_dim(); if (i < axis) { if (indices_shape.dim(i).has_dim_value()) { dim->set_dim_value(indices_shape.dim(i).dim_value()); } else if (indices_shape.dim(i).has_dim_param()) { dim->set_dim_param(indices_shape.dim(i).dim_param()); } } else if (i > axis) { if (indices_shape.dim(i - 1).has_dim_value()) { dim->set_dim_value(indices_shape.dim(i - 1).dim_value()); } else if (indices_shape.dim(i - 1).has_dim_param()) { dim->set_dim_param(indices_shape.dim(i - 1).dim_param()); } } } } })); static const char* Compress_ver9_doc = R"DOC( Selects slices from an input tensor along a given axis where condition evaluates to True for each axis index. In case axis is not provided, input is flattened before elements are selected. Compress behaves like numpy.compress: https://docs.scipy.org/doc/numpy/reference/generated/numpy.compress.html )DOC"; ONNX_OPERATOR_SET_SCHEMA( Compress, 9, OpSchema() .SetDoc(Compress_ver9_doc) .Attr( "axis", "(Optional) Axis along which to take slices. If not specified, " "input is flattened before elements being selected.", AttributeProto::INT, OPTIONAL_VALUE) .Input(0, "input", "Tensor of rank r >= 1.", "T") .Input( 1, "condition", "Rank 1 tensor of booleans to indicate which slices or data elements to be selected. " "Its length can be less than the input length alone the axis " "or the flattened input size if axis is not specified. " "In such cases data slices or elements exceeding the condition length are discarded.", "T1") .Output( 0, "output", "Tensor of rank r if axis is specified. Otherwise output is a Tensor of rank 1.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains to boolean tensors.")); static const char* Split_ver2_doc = R"DOC(Split a tensor into a list of tensors, along the specified 'axis'. Lengths of the parts can be specified using argument 'split'. Otherwise, the tensor is split to equal sized parts. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Split, 2, OpSchema() .Input(0, "input", "The tensor to split", "T") .Output( 0, "outputs", "One or more outputs forming list of tensors after splitting", "T", OpSchema::Variadic) .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .Attr( "axis", "Which axis to split on. ", AttributeProto::INT, static_cast(0)) .Attr("split", "length of each output", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Split_ver2_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { for (int i = 0; i < static_cast(ctx.getNumOutputs()); ++i) { propagateElemTypeFromInputToOutput(ctx, 0, i); } if (!hasNInputShapes(ctx, 1)) { return; } auto axisAttr = ctx.getAttribute("axis"); int axis = axisAttr ? static_cast(axisAttr->i()) : 0; if (axis < 0) { return; } std::vector split; if (!getRepeatedAttribute(ctx, "split", split)) { if (!ctx.getInputType(0)->tensor_type().has_shape()) { return; } const auto& shape = ctx.getInputType(0)->tensor_type().shape(); if (axis >= shape.dim_size()) { fail_type_inference("Invalid value of attribute 'axis'"); } const auto& splitDim = shape.dim(axis); if (!splitDim.has_dim_value()) { return; } int splitDimValue = static_cast(splitDim.dim_value()); int chunkSize = splitDimValue / static_cast(ctx.getNumOutputs()); int leftOver = splitDimValue - (chunkSize * static_cast(ctx.getNumOutputs())); for (int i = 0; i < static_cast(ctx.getNumOutputs()); i++) { split.push_back(i < leftOver ? chunkSize + 1 : chunkSize); } for (size_t i = 0; i < ctx.getNumOutputs(); i++) { *ctx.getOutputType(i)->mutable_tensor_type()->mutable_shape() = shape; ctx.getOutputType(i) ->mutable_tensor_type() ->mutable_shape() ->mutable_dim(axis) ->set_dim_value(split[i]); } } })); static const char* Pad_ver2_doc = R"DOC( Given `data` tensor, pads, mode, and value. Example: Insert 0 pads to the beginning of the second dimension. data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] pads = [0, 2, 0, 0] output = [ [ [0.0, 0.0, 1.0, 1.2], [0.0, 0.0, 2.3, 3.4], [0.0, 0.0, 4.5, 5.7], ], ] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Pad, 2, OpSchema() .Attr( "pads", "List of integers indicating the number of padding elements to add or remove (if negative) " "at the beginning and end of each axis. For 2D it is the number of pixels. " "`pads` rank should be double of the input's rank. `pads` format should be as follow " "[x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of pixels " "added at the beginning of axis `i` and xi_end, the number of pixels added at " "the end of axis `i`.", AttributeProto::INTS) .Attr( "mode", "Three modes: constant(default), reflect, edge", AttributeProto::STRING, std::string("constant")) .Attr( "value", "One float, indicates the value to be filled.", AttributeProto::FLOAT, 0.0f) .SetDoc(Pad_ver2_doc) .Input(0, "data", "Input tensor.", "T") .Output(0, "output", "Tensor after padding.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); std::vector pads; if (!getRepeatedAttribute(ctx, "pads", pads)) fail_shape_inference("Attribute value for pads is required"); if (pads.size() != static_cast(input_shape.dim_size() * 2)) { fail_shape_inference("Attribute pads has incorrect length"); ; } ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); for (size_t i = 0; (int64_t)i < input_shape.dim_size(); ++i) { auto* newdim = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim(); if (ctx.getInputType(0) ->tensor_type() .shape() .dim((int)i) .has_dim_value()) { newdim->set_dim_value( ctx.getInputType(0) ->tensor_type() .shape() .dim((int)i) .dim_value() + pads[i] + pads[input_shape.dim_size() + i]); } else if (pads[i] + pads[input_shape.dim_size() + i] == 0) { *newdim = input_shape.dim((int)i); } } })); static const char* GatherND_ver11_doc = R"DOC( Given `data` tensor of rank `r` >= 1, and `indices` tensor of rank `q` >= 1, this operator gathers slices of `data` into an output tensor of rank `q + r - indices_shape[-1] - 1`. `indices` is an q-dimensional integer tensor, best thought of as a `(q-1)`-dimensional tensor of index-tuples into `data`, where each element defines a slice of `data` Some salient points about the inputs' rank and shape: 1) r >= 1 and q >= 1 are to be honored. There is no dependency condition to be met between ranks `r` and `q` 2) The `indices_shape[-1]` should have a value between 1 (inclusive) and rank `r` (inclusive) 3) All values in `indices` are expected to be within bounds [-s, s-1] along axis of size `s` (i.e.) `-data_shape[i] <= indices[...,i] <= data_shape[i] - 1`. It is an error if any of the index values are out of bounds. The output is computed as follows: The output tensor is obtained by mapping each index-tuple in the `indices` tensor to the corresponding slice of the input `data`. 1) If `indices_shape[-1] > r` => error condition 2) If `indices_shape[-1] == r`, since the rank of `indices` is `q`, `indices` can be thought of as a `(q-1)`-dimensional tensor containing 1-D tensors of dimension `r`. Let us think of each such `r` ranked tensor as `indices_slice`. Each *scalar value* corresponding to `data[indices_slice]` is filled into the corresponding location of the `(q-1)`-dimensional tensor to form the `output` tensor (Example 1 below) 3) If `indices_shape[-1] < r`, since the rank of `indices` is `q`, `indices` can be thought of as a `(q-1)`-dimensional tensor containing 1-D tensors of dimension `< r`. Let us think of each such tensors as `indices_slice`. Each *tensor slice* corresponding to `data[indices_slice , :]` is filled into the corresponding location of the `(q-1)`-dimensional tensor to form the `output` tensor (Examples 2, 3, and 4 below) This operator is the inverse of `ScatterND`. `Example 1` data = [[0,1],[2,3]] # data_shape = [2, 2] indices = [[0,0],[1,1]] # indices_shape = [2, 2] output = [0,3] # output_shape = [2] `Example 2` data = [[0,1],[2,3]] # data_shape = [2, 2] indices = [[1],[0]] # indices_shape = [2, 1] output = [[2,3],[0,1]] # output_shape = [2, 2] `Example 3` data = [[[0,1],[2,3]],[[4,5],[6,7]]] # data_shape = [2, 2, 2] indices = [[0,1],[1,0]] # indices_shape = [2, 2] output = [[2,3],[4,5]] # output_shape = [2, 2] `Example 4` data = [[[0,1],[2,3]],[[4,5],[6,7]]] # data_shape = [2, 2, 2] indices = [[[0,1]],[[1,0]]] # indices_shape = [2, 1, 2] output = [[[2,3]],[[4,5]]] # output_shape = [2, 1, 2] )DOC"; ONNX_OPERATOR_SET_SCHEMA( GatherND, 11, OpSchema() .SetDoc(GatherND_ver11_doc) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of rank q >= 1. All index values are expected to be within bounds [-s, s-1] " "along axis of size s. It is an error if any of the index values are out of bounds.", "tensor(int64)") .Output( 0, "output", "Tensor of rank q + r - indices_shape[-1] - 1.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference if (!hasNInputShapes(ctx, 2)) { // cannot proceed with shape or rank inference return; } const auto& data_shape = ctx.getInputType(0)->tensor_type().shape(); const auto data_rank = data_shape.dim_size(); const auto& indices_shape = ctx.getInputType(1)->tensor_type().shape(); const auto indices_rank = indices_shape.dim_size(); if (data_rank < 1 || indices_rank < 1) { fail_shape_inference( "Both `data` and `indices` input tensors in GatherND op " "need to have rank larger than 0."); } // cannot ascertain if the input shapes are valid if shape of // `indices` is missing last dimension value so return at this point if (!indices_shape.dim(indices_rank - 1).has_dim_value()) { return; } const auto last_index_dimension = indices_shape.dim(indices_rank - 1).dim_value(); if (last_index_dimension > data_rank) { fail_shape_inference( "Last dimension of `indices` input tensor in GatherND op " "must not be larger than the rank of `data` tensor"); } for (int i = 0; i < indices_rank - 1; ++i) { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = indices_shape.dim(i); } for (int i = static_cast(last_index_dimension); i < data_rank; ++i) { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = data_shape.dim(i); } })); } // namespace ONNX_NAMESPACEonnx-1.7.0/onnx/defs/tensor/defs.cc0000664000000000000000000032306013655345213015661 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/tensor/utils.h" #include #include #include namespace ONNX_NAMESPACE { static const char* Cast_ver9_doc = R"DOC( The operator casts the elements of a given input tensor to a data type specified by the 'to' argument and returns an output tensor of the same size in the converted type. The 'to' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message. Casting from string tensor in plain (e.g., "3.14" and "1000") and scientific numeric representations (e.g., "1e-5" and "1E8") to float types is supported. For example, converting string "100.5" to an integer may result 100. There are some string literals reserved for special floating-point values; "+INF" (and "INF"), "-INF", and "NaN" are positive infinity, negative infinity, and not-a-number, respectively. Any string which can exactly match "+INF" in a case-insensitive way would be mapped to positive infinite. Similarly, this case-insensitive rule is applied to "INF" and "NaN". When casting from numeric tensors to string tensors, plain floating-point representation (such as "314.15926") would be used. Converting non-numerical-literal string such as "Hello World!" is an undefined behavior. Cases of converting string representing floating-point arithmetic value, such as "2.718", to INT is an undefined behavior. Conversion from a numerical type to any numerical type is always allowed. User must be aware of precision loss and value change caused by range difference between two types. For example, a 64-bit float 3.1415926459 may be round to a 32-bit float 3.141592. Similarly, converting an integer 36 to Boolean may produce 1 because we truncate bits which can't be stored in the targeted type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Cast, 9, OpSchema() .SetDoc(Cast_ver9_doc) .Attr( "to", "The data type to which the elements of the input tensor are cast. " "Strictly must be one of the types from DataType enum in TensorProto", AttributeProto::INT) .Input(0, "input", "Input tensor to be cast.", "T1") .Output( 0, "output", "Output tensor with the same shape as input with type " "specified by the 'to' argument", "T2") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)", "tensor(string)"}, "Constrain input types. Casting from complex is not supported.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)", "tensor(string)"}, "Constrain output types. Casting to complex is not supported.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromAttributeToOutput(ctx, "to", 0); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* Reshape_ver5_doc = R"DOC( Reshape the input tensor similar to numpy.reshape. First input is the data tensor, second input is a shape tensor which specifies the output shape. It outputs the reshaped tensor. At most one dimension of the new shape can be -1. In this case, the value is inferred from the size of the tensor and the remaining dimensions. A dimension could also be 0, in which case the actual dimension value is unchanged (i.e. taken from the input tensor).)DOC"; ONNX_OPERATOR_SET_SCHEMA( Reshape, 5, OpSchema() .SetDoc(Reshape_ver5_doc) .Input(0, "data", "An input tensor.", "T") .Input(1, "shape", "Specified shape for output.", "tensor(int64)") .Output(0, "reshaped", "Reshaped data.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape Inference if 2nd input data (the target shape) is available const TensorProto* targetShapeInitializer = ctx.getInputData(1); if (!targetShapeInitializer) { return; } // Make targetShape (0 -> same as originalShape, -1 -> inferred). // The targetShape vector represents the specified shape for output. std::vector targetShape; if (targetShapeInitializer->has_raw_data()) { const std::string& bytes = targetShapeInitializer->raw_data(); targetShape.insert( targetShape.end(), reinterpret_cast(bytes.c_str()), reinterpret_cast(bytes.c_str() + bytes.size())); } else { const auto& data = targetShapeInitializer->int64_data(); targetShape.insert(targetShape.end(), data.begin(), data.end()); } // Iterate through targetShape, adding dimensions in the outputShape // TensorProto. If the targertShape dimension is -1, we do not set the // dimension value in this iteration, but we record the Dimension. If // targertShape dimension is 0, we attempt to propagate the dimension // value/param. If the value cannot be inferred, we set the flag in // the unresolveZeros vector. If targetShape dimension is positive, we // set the dimension value in the outputShape. We track the product of // the dimensions we are setting outputShape in the outputProduct // variable. The outputProduct will potentially be used for inferring // a dimension marked -1. auto* outputShape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); TensorShapeProto::Dimension* negativeOneDim = nullptr; const auto& dataInputTensorType = ctx.getInputType(0)->tensor_type(); std::vector unresolvedZeros(targetShape.size(), false); int64_t outputProduct = 1; for (int i = 0; i < static_cast(targetShape.size()); ++i) { // Add a new dimension to outputShape auto* new_dim = outputShape->add_dim(); if (targetShape[i] == -1) { // Check if multiple -1's. If not, set negativeOneDim, marking // this dimension to potentially be filled in later. if (negativeOneDim) { fail_shape_inference( "Target shape may not have multiple -1 dimensions"); } negativeOneDim = new_dim; } else if (targetShape[i] == 0) { // Check if data input has a shape and if the index i is within // its bounds. If these conditions are satisfied, any dimension // value/param should be propogated. If dimension value cannot be // inferred, set the corresponding unresolvedZeros flag to true. unresolvedZeros[i] = true; if (dataInputTensorType.has_shape()) { if (i >= dataInputTensorType.shape().dim_size()) { fail_shape_inference("Invalid position of 0"); } if (dataInputTensorType.shape().dim(i).has_dim_value()) { const auto& dim_value = dataInputTensorType.shape().dim(i).dim_value(); new_dim->set_dim_value(dim_value); outputProduct *= dim_value; unresolvedZeros[i] = false; } else if (dataInputTensorType.shape().dim(i).has_dim_param()) { const auto& dim_param = dataInputTensorType.shape().dim(i).dim_param(); new_dim->set_dim_param(dim_param); } } } else if (targetShape[i] > 0) { // Set the dimension value to targetShape[i] new_dim->set_dim_value(targetShape[i]); outputProduct *= targetShape[i]; } else { // Check if value is less than -1; fail if so fail_shape_inference("Invalid dimension value: ", targetShape[i]); } } // If negativeOneDim has been set, we attempt to infer its value. This // can be done if all dimension values for the data input tensor shape // are known other than the ones corresponding to unresolvedZeros // flags. if (negativeOneDim) { // First, attempt to compute product of data input shape dimensions // that are not marked by unresolvedZeros. If not possible, set the // inputProductValid flag to false. if (!outputProduct) { fail_shape_inference("Invalid Target shape product of 0"); } int64_t inputProduct = 1; bool inputProductValid = true; if (!dataInputTensorType.has_shape()) { inputProductValid = false; } else { for (int i = 0; i < dataInputTensorType.shape().dim_size(); ++i) { if (dataInputTensorType.shape().dim(i).has_dim_value()) { inputProduct *= dataInputTensorType.shape().dim(i).dim_value(); } else if ( i >= static_cast(unresolvedZeros.size()) || !unresolvedZeros[i]) { inputProductValid = false; break; } } } if (inputProductValid) { if (inputProduct % outputProduct != 0) { fail_shape_inference( "Dimension could not be inferred: incompatible shapes"); } negativeOneDim->set_dim_value(inputProduct / outputProduct); } } })); static const char* Shape_ver1_doc = R"DOC( Takes a tensor as input and outputs an 1D int64 tensor containing the shape of the input tensor. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Shape, 1, OpSchema() .SetDoc(Shape_ver1_doc) .Input(0, "data", "An input tensor.", "T") .Output(0, "shape", "Shape of the input tensor", "T1") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input tensor can be of arbitrary type.") .TypeConstraint( "T1", {"tensor(int64)"}, "Constrain output to int64 tensor.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { ctx.getOutputType(0)->mutable_tensor_type()->set_elem_type( TensorProto::INT64); if (!hasNInputShapes(ctx, 1)) { return; } if (ctx.getInputType(0)->tensor_type().has_shape()) { ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() ->set_dim_value( ctx.getInputType(0)->tensor_type().shape().dim_size()); } })); static const char* Size_ver1_doc = R"DOC( Takes a tensor as input and outputs a int64 scalar that equals to the total number of elements of the input tensor. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Size, 1, OpSchema() .SetDoc(Size_ver1_doc) .Input(0, "data", "An input tensor.", "T") .Output(0, "size", "Total number of elements of the input tensor", "T1") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input tensor can be of arbitrary type.") .TypeConstraint( "T1", {"tensor(int64)"}, "Constrain output to int64 tensor, which should be a scalar though.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { ctx.getOutputType(0)->mutable_tensor_type()->set_elem_type( TensorProto::INT64); ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); })); ONNX_OPERATOR_SET_SCHEMA( Concat, 11, OpSchema() .Attr( "axis", "Which axis to concat on. A negative value means counting dimensions from the back. " "Accepted range is [-r, r-1] where r = rank(inputs)..", AttributeProto::INT) .SetDoc( "Concatenate a list of tensors into a single tensor. " "All input tensors must have the same shape, except for the dimension size of the axis to concatenate on.") .Input( 0, "inputs", "List of tensors for concatenation", "T", OpSchema::Variadic) .Output(0, "concat_result", "Concatenated tensor", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); auto numInputs = ctx.getNumInputs(); if (numInputs < 1 || !hasNInputShapes(ctx, static_cast(numInputs))) { return; } auto rank = ctx.getInputType(0)->tensor_type().shape().dim_size(); auto axisAttr = ctx.getAttribute("axis"); if (!axisAttr) { fail_shape_inference("Required attribute axis is missing"); } int axis = static_cast(axisAttr->i()); if (axis < -rank || axis >= rank) { fail_shape_inference("axis must be in [-rank, rank-1]."); } if (axis < 0) { axis += rank; } if (numInputs == 1) { propagateShapeFromInputToOutput(ctx, 0, 0); return; } bool all_lengths_known = true; int total_length = 0; auto* output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); for (int64_t i = 0; i < rank; ++i) { output_shape->add_dim(); } for (size_t i = 0; i < numInputs; i++) { const auto& shape = ctx.getInputType(i)->tensor_type().shape(); if (shape.dim_size() != rank) fail_shape_inference("All inputs to Concat must have same rank"); for (int j = 0; j < rank; j++) { if (j == axis) { if (shape.dim(j).has_dim_value()) { total_length += static_cast(shape.dim(j).dim_value()); } else { all_lengths_known = false; } } else { auto& output_dim = *output_shape->mutable_dim(j); const auto& input_dim = shape.dim(j); mergeInDimensionInfo(input_dim, output_dim, j); } } } if (all_lengths_known) { output_shape->mutable_dim(axis)->set_dim_value(total_length); } })); static const char* Split_ver11_doc = R"DOC(Split a tensor into a list of tensors, along the specified 'axis'. Lengths of the parts can be specified using argument 'split'. Otherwise, the tensor is split to equal sized parts. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Split, 11, OpSchema() .Input(0, "input", "The tensor to split", "T") .Output( 0, "outputs", "One or more outputs forming list of tensors after splitting", "T", OpSchema::Variadic) .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .Attr( "axis", "Which axis to split on. " "A negative value means counting dimensions from the back. Accepted range is [-rank, rank-1] " "where r = rank(input).", AttributeProto::INT, static_cast(0)) .Attr("split", "length of each output. Values should be >= 0.", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Split_ver11_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { for (int i = 0; i < static_cast(ctx.getNumOutputs()); ++i) { propagateElemTypeFromInputToOutput(ctx, 0, i); } if (!hasNInputShapes(ctx, 1)) { return; } const auto& shape = ctx.getInputType(0)->tensor_type().shape(); int rank = shape.dim_size(); int axis = static_cast(getAttribute(ctx, "axis", 0)); if (axis < -rank || axis >= rank) { fail_type_inference( "Invalid value of attribute 'axis'. Rank=", rank, " Value=", axis); } if (axis < 0) { axis += rank; } const auto& split_dim = shape.dim(axis); if (!split_dim.has_dim_value()) { for (size_t i = 0; i < ctx.getNumOutputs(); i++) { *ctx.getOutputType(i)->mutable_tensor_type()->mutable_shape() = shape; ctx.getOutputType(i) ->mutable_tensor_type() ->mutable_shape() ->mutable_dim(axis) ->Clear(); } return; } int split_dim_value = static_cast(split_dim.dim_value()); std::vector split; if (getRepeatedAttribute(ctx, "split", split)) { if (split.size() != ctx.getNumOutputs()) { fail_shape_inference( "Mismatch between number of splits (", split.size(), ") and outputs (", ctx.getNumOutputs(), ")"); } int64_t total_dim = 0; for (int64_t d : split) { total_dim += d; } if (total_dim != split_dim_value) { fail_shape_inference( "Mismatch between the sum of 'split' (", total_dim, ") and the split dimension of the input (", split_dim_value, ")"); } } else { int num_outputs = static_cast(ctx.getNumOutputs()); if (split_dim_value % num_outputs != 0) { fail_shape_inference("The input is not evenly splittable"); } int chunk_size = split_dim_value / num_outputs; for (int i = 0; i < static_cast(ctx.getNumOutputs()); i++) { split.push_back(chunk_size); } } for (size_t i = 0; i < ctx.getNumOutputs(); i++) { *ctx.getOutputType(i)->mutable_tensor_type()->mutable_shape() = shape; ctx.getOutputType(i) ->mutable_tensor_type() ->mutable_shape() ->mutable_dim(axis) ->set_dim_value(split[i]); } })); static const char* Slice_ver11_doc = R"DOC( Produces a slice of the input tensor along multiple axes. Similar to numpy: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html Slices uses `starts`, `ends`, `axes` and `steps` inputs to specify the start and end dimension and step for each axis in the list of axes, it uses this information to slice the input `data` tensor. If a negative value is passed for any of the start or end indices, it represents number of elements before the end of that dimension. If the value passed to start or end is larger than the `n` (the number of elements in this dimension), it represents `n`. For slicing to the end of a dimension with unknown size, it is recommended to pass in `INT_MAX` when sclicing forward and 'INT_MIN' when slicing backward. If a negative value is passed for step, it represents slicing backward. However step value cannot be 0. If `axes` are omitted, they are set to `[0, ..., ndim-1]`. If `steps` are omitted, they are set to `[1, ..., 1]` of length `len(starts)` Example 1: data = [ [1, 2, 3, 4], [5, 6, 7, 8], ] axes = [0, 1] starts = [1, 0] ends = [2, 3] steps = [1, 2] result = [ [5, 7], ] Example 2: data = [ [1, 2, 3, 4], [5, 6, 7, 8], ] starts = [0, 1] ends = [-1, 1000] result = [ [2, 3, 4], ] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Slice, 11, OpSchema() .SetDoc(Slice_ver11_doc) .Input(0, "data", "Tensor of data to extract slices from.", "T") .Input( 1, "starts", "1-D tensor of starting indices of corresponding axis in `axes`", "Tind") .Input( 2, "ends", "1-D tensor of ending indices (exclusive) of corresponding axis in `axes`", "Tind") .Input( 3, "axes", "1-D tensor of axes that `starts` and `ends` apply to. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(data).", "Tind", OpSchema::Optional) .Input( 4, "steps", "1-D tensor of slice step of corresponding axis in `axes`. " "Negative value means slicing backward. 'steps' cannot be 0. " "Defaults to 1.", "Tind", OpSchema::Optional) .Output(0, "output", "Sliced data tensor.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { size_t num_inputs = ctx.getNumInputs(); if (num_inputs != 3 && num_inputs != 4 && num_inputs != 5) { fail_type_inference( "Slice op must have either three, four or five inputs."); } propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } // Shape Inference if // 1. 2nd and 3rd input data (starts, ends) are available. // and 2. 4th and 5th optional input (axes, steps) are either not set, // or set and is initializer. const TensorProto* startsInitializer = ctx.getInputData(1); const TensorProto* endsInitializer = ctx.getInputData(2); const TensorProto* axesInitializer = hasInputShape(ctx, 3) ? ctx.getInputData(3) : nullptr; const TensorProto* stepsInitializer = hasInputShape(ctx, 4) ? ctx.getInputData(4) : nullptr; if (!startsInitializer || !endsInitializer || (hasInputShape(ctx, 3) && !ctx.getInputData(3)) || (hasInputShape(ctx, 4) && !ctx.getInputData(4))) { return; } // don't know data_type- can't proceed if (!startsInitializer->has_data_type()) return; auto get_initializer_data = [](const TensorProto* initializer) -> std::vector { std::vector vec; if (initializer->data_type() == TensorProto::INT64) { const auto& data = ParseData(initializer); vec.insert(vec.end(), data.begin(), data.end()); } else if (initializer->data_type() == TensorProto::INT32) { const auto& data = ParseData(initializer); vec.insert(vec.end(), data.begin(), data.end()); } else { // unaccepted data type fail_shape_inference( "Only supports `int32_t` or `int64_t` inputs for starts/ends/axes/steps"); } return vec; }; auto clamp = [](int64_t val, int64_t low, int64_t high) -> int64_t { if (val < low) return low; if (val > high) return high; return val; }; std::vector starts = get_initializer_data(startsInitializer); std::vector ends = get_initializer_data(endsInitializer); if (starts.size() != ends.size()) { fail_shape_inference( "Incorrect or missing input value for starts and ends"); } const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_rank = input_shape.dim_size(); std::vector axes(starts.size()); if (!axesInitializer) { std::iota(axes.begin(), axes.end(), 0); } else { axes = get_initializer_data(axesInitializer); if (axes.size() != starts.size()) { fail_shape_inference("Input axes has incorrect length"); } } std::vector steps; if (!stepsInitializer) { steps = std::vector(starts.size(), 1); } else { steps = get_initializer_data(stepsInitializer); if (steps.size() != axes.size()) { fail_shape_inference("Input steps has incorrect length"); } } for (size_t i = 0; (int64_t)i < input_rank; ++i) { // first update rank of output dim auto* output_dim = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim(); const auto& input_dim = input_shape.dim((int)i); if (input_dim.has_dim_value()) { output_dim->set_dim_value(input_dim.dim_value()); } else if (input_dim.has_dim_param()) { output_dim->set_dim_param(input_dim.dim_param()); } } std::unordered_set unique_axes; size_t axes_size = axes.size(); for (size_t axis_index = 0; axis_index < axes_size; ++axis_index) { auto axis = axes[axis_index] < 0 ? axes[axis_index] + static_cast(input_rank) : axes[axis_index]; if (axis >= static_cast(input_rank) || axis < 0) fail_shape_inference("Input axes has invalid data"); if (unique_axes.find(axis) != unique_axes.end()) fail_shape_inference("'axes' has duplicates"); unique_axes.insert(axis); auto input_dim = ctx.getInputType(0)->tensor_type().shape().dim((int)axis); // input dim value is missing - cannot perform shape inference for // this axis if (!input_dim.has_dim_value()) { // Clear any previously propagated dim_param and leave this dimension "empty", // before moving on to the next dimension ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->mutable_dim(static_cast(axis)) ->clear_dim_param(); continue; } const auto input_dim_value = input_dim.dim_value(); // process step auto step = steps[axis_index]; if (step == 0) fail_shape_inference("'step' cannot be 0"); // process start auto start = starts[axis_index]; if (start < 0) start += input_dim_value; if (step < 0) start = clamp(start, 0, input_dim_value - 1); else start = clamp(start, 0, input_dim_value); // process end auto end = ends[axis_index]; if (end < 0) end += input_dim_value; if (step < 0) end = clamp(end, -1, input_dim_value); else end = clamp(end, 0, input_dim_value); // find output dim value for this axis auto temp = static_cast(ceil(1.0 * (end - start) / step)); if (temp < 0) temp = 0; // assign output value ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->mutable_dim((int)axis) ->set_dim_value(temp); } })); static const char* Transpose_ver1_doc = R"DOC( Transpose the input tensor similar to numpy.transpose. For example, when perm=(1, 0, 2), given an input tensor of shape (1, 2, 3), the output shape will be (2, 1, 3). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Transpose, 1, OpSchema() .SetDoc(Transpose_ver1_doc) .Attr( "perm", "A list of integers. By default, reverse the dimensions, " "otherwise permute the axes according to the values given.", AttributeProto::INTS, OPTIONAL_VALUE) .Input(0, "data", "An input tensor.", "T") .Output(0, "transposed", "Transposed output.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } auto input_type = ctx.getInputType(0); const TensorShapeProto& shape = input_type->tensor_type().shape(); std::vector perm; bool has_perm_attr = getRepeatedAttribute(ctx, "perm", perm); if (!has_perm_attr) { for (int i = shape.dim_size() - 1; i >= 0; --i) perm.push_back(i); } else if (!perm.empty()) { // check if every index is valid for (int64_t fromDimIndex : perm) if (!(0 <= fromDimIndex && fromDimIndex < shape.dim_size())) { std::ostringstream oss; oss << "Invalid attribute perm {" << perm[0]; for (size_t i = 1; i != perm.size(); ++i) { oss << ", " << perm[i]; } oss << "}, input shape = {"; if (shape.dim_size() > 0) { oss << shape.dim(0).dim_value(); for (int i = 1; i != shape.dim_size(); ++i) { oss << ", " << shape.dim(i).dim_value(); } oss << "}"; } fail_type_inference(oss.str()); } } propagateElemTypeFromInputToOutput(ctx, 0, 0); for (size_t i = 0; i < perm.size(); ++i) { appendSingleDimCopiedFromInputTypeToOutputType( ctx, 0, 0, static_cast(perm[i])); } })); static const char* Scatter_ver11_doc = R"DOC( This operator is deprecated. Please use ScatterElements, which provides the same functionality. Scatter takes three inputs `data`, `updates`, and `indices` of the same rank r >= 1 and an optional attribute axis that identifies an axis of `data` (by default, the outer-most axis, that is axis 0). The output of the operation is produced by creating a copy of the input `data`, and then updating its value to values specified by `updates` at specific index positions specified by `indices`. Its output shape is the same as the shape of `data`. For each entry in `updates`, the target index in `data` is obtained by combining the corresponding entry in `indices` with the index of the entry itself: the index-value for dimension = axis is obtained from the value of the corresponding entry in `indices` and the index-value for dimension != axis is obtained from the index of the entry itself. For instance, in a 2-D tensor case, the update corresponding to the [i][j] entry is performed as below: ``` output[indices[i][j]][j] = updates[i][j] if axis = 0, output[i][indices[i][j]] = updates[i][j] if axis = 1, ``` This operator is the inverse of GatherElements. It is similar to Torch's Scatter operation. Example 1: ``` data = [ [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], ] indices = [ [1, 0, 2], [0, 2, 1], ] updates = [ [1.0, 1.1, 1.2], [2.0, 2.1, 2.2], ] output = [ [2.0, 1.1, 0.0] [1.0, 0.0, 2.2] [0.0, 2.1, 1.2] ] ``` Example 2: ``` data = [[1.0, 2.0, 3.0, 4.0, 5.0]] indices = [[1, 3]] updates = [[1.1, 2.1]] axis = 1 output = [[1.0, 1.1, 3.0, 2.1, 5.0]] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( Scatter, 11, OpSchema() .Deprecate() .SetDoc(Scatter_ver11_doc) .Attr( "axis", "Which axis to scatter on. Negative value means " "counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of int32/int64 indices, of r >= 1 (same rank as input). All index values are expected to be " "within bounds [-s, s-1] along axis of size s. It is an error if any of the index values are out of bounds.", "Tind") .Input( 2, "updates", "Tensor of rank r >=1 (same rank and shape as indices)", "T") .Output(0, "output", "Tensor of rank r >= 1 (same rank as input).", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input and output types can be of any tensor type.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* ScatterND_ver11_doc = R"DOC( ScatterND takes three inputs `data` tensor of rank r >= 1, `indices` tensor of rank q >= 1, and `updates` tensor of rank q + r - indices.shape[-1] - 1. The output of the operation is produced by creating a copy of the input `data`, and then updating its value to values specified by `updates` at specific index positions specified by `indices`. Its output shape is the same as the shape of `data`. Note that `indices` should not have duplicate entries. That is, two or more `updates` for the same index-location is not supported. `indices` is an integer tensor. Let k denote indices.shape[-1], the last dimension in the shape of `indices`. `indices` is treated as a (q-1)-dimensional tensor of k-tuples, where each k-tuple is a partial-index into `data`. Hence, k can be a value at most the rank of `data`. When k equals rank(data), each update entry specifies an update to a single element of the tensor. When k is less than rank(data) each update entry specifies an update to a slice of the tensor. `updates` is treated as a (q-1)-dimensional tensor of replacement-slice-values. Thus, the first (q-1) dimensions of updates.shape must match the first (q-1) dimensions of indices.shape. The remaining dimensions of `updates` correspond to the dimensions of the replacement-slice-values. Each replacement-slice-value is a (r-k) dimensional tensor, corresponding to the trailing (r-k) dimensions of `data`. Thus, the shape of `updates` must equal indices.shape[0:q-1] ++ data.shape[k:r-1], where ++ denotes the concatenation of shapes. The `output` is calculated via the following equation: output = np.copy(data) update_indices = indices.shape[:-1] for idx in np.ndindex(update_indices): output[indices[idx]] = updates[idx] The order of iteration in the above loop is not specified. In particular, indices should not have duplicate entries: that is, if idx1 != idx2, then indices[idx1] != indices[idx2]. This ensures that the output value does not depend on the iteration order. This operator is the inverse of GatherND. Example 1: ``` data = [1, 2, 3, 4, 5, 6, 7, 8] indices = [[4], [3], [1], [7]] updates = [9, 10, 11, 12] output = [1, 11, 3, 10, 9, 6, 7, 12] ``` Example 2: ``` data = [[[1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5], [4, 3, 2, 1]], [[1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5], [4, 3, 2, 1]], [[8, 7, 6, 5], [4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8]], [[8, 7, 6, 5], [4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8]]] indices = [[0], [2]] updates = [[[5, 5, 5, 5], [6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8]], [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]] output = [[[5, 5, 5, 5], [6, 6, 6, 6], [7, 7, 7, 7], [8, 8, 8, 8]], [[1, 2, 3, 4], [5, 6, 7, 8], [8, 7, 6, 5], [4, 3, 2, 1]], [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]], [[8, 7, 6, 5], [4, 3, 2, 1], [1, 2, 3, 4], [5, 6, 7, 8]]] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( ScatterND, 11, OpSchema() .SetDoc(ScatterND_ver11_doc) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input(1, "indices", "Tensor of rank q >= 1.", "tensor(int64)") .Input( 2, "updates", "Tensor of rank q + r - indices_shape[-1] - 1.", "T") .Output(0, "output", "Tensor of rank r >= 1.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* ScatterElements_ver11_doc = R"DOC( ScatterElements takes three inputs `data`, `updates`, and `indices` of the same rank r >= 1 and an optional attribute axis that identifies an axis of `data` (by default, the outer-most axis, that is axis 0). The output of the operation is produced by creating a copy of the input `data`, and then updating its value to values specified by `updates` at specific index positions specified by `indices`. Its output shape is the same as the shape of `data`. For each entry in `updates`, the target index in `data` is obtained by combining the corresponding entry in `indices` with the index of the entry itself: the index-value for dimension = axis is obtained from the value of the corresponding entry in `indices` and the index-value for dimension != axis is obtained from the index of the entry itself. For instance, in a 2-D tensor case, the update corresponding to the [i][j] entry is performed as below: ``` output[indices[i][j]][j] = updates[i][j] if axis = 0, output[i][indices[i][j]] = updates[i][j] if axis = 1, ``` This operator is the inverse of GatherElements. It is similar to Torch's Scatter operation. Example 1: ``` data = [ [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], ] indices = [ [1, 0, 2], [0, 2, 1], ] updates = [ [1.0, 1.1, 1.2], [2.0, 2.1, 2.2], ] output = [ [2.0, 1.1, 0.0] [1.0, 0.0, 2.2] [0.0, 2.1, 1.2] ] ``` Example 2: ``` data = [[1.0, 2.0, 3.0, 4.0, 5.0]] indices = [[1, 3]] updates = [[1.1, 2.1]] axis = 1 output = [[1.0, 1.1, 3.0, 2.1, 5.0]] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( ScatterElements, 11, OpSchema() .SetDoc(ScatterElements_ver11_doc) .Attr( "axis", "Which axis to scatter on. Negative value means " "counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of int32/int64 indices, of r >= 1 (same rank as input). All index values are expected to be " "within bounds [-s, s-1] along axis of size s. It is an error if any of the index values are out of bounds.", "Tind") .Input( 2, "updates", "Tensor of rank r >=1 (same rank and shape as indices)", "T") .Output(0, "output", "Tensor of rank r >= 1 (same rank as input).", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input and output types can be of any tensor type.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* Gather_ver11_doc = R"DOC( Given `data` tensor of rank r >= 1, and `indices` tensor of rank q, gather entries of the axis dimension of `data` (by default outer-most one as axis=0) indexed by `indices`, and concatenates them in an output tensor of rank q + (r - 1). axis = 0 : Let k = indices[i_{0}, ..., i_{q-1}] Then output[i_{0}, ..., i_{q-1}, j_{0}, ..., j_{r-2}] = input[k , j_{0}, ..., j_{r-2}] ``` data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] indices = [ [0, 1], [1, 2], ] output = [ [ [1.0, 1.2], [2.3, 3.4], ], [ [2.3, 3.4], [4.5, 5.7], ], ] ``` axis = 1 : Let k = indices[i_{0}, ..., i_{q-1}] Then output[i_{0}, ..., i_{q-1}, j_{0}, ..., j_{r-2}] = input[j_{0}, k, j_{1}, ..., j_{r-2}] ``` data = [ [1.0, 1.2, 1.9], [2.3, 3.4, 3.9], [4.5, 5.7, 5.9], ] indices = [ [0, 2], ] axis = 1, output = [ [ [1.0, 1.9], [2.3, 3.9], [4.5, 5.9], ], ] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gather, 11, OpSchema() .SetDoc(Gather_ver11_doc) .Attr( "axis", "Which axis to gather on. Negative value means " "counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of int32/int64 indices, of any rank q. All index values are expected to be within bounds [-s, s-1] " "along axis of size s. It is an error if any of the index values are out of bounds.", "Tind") .Output(0, "output", "Tensor of rank q + (r - 1).", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to any tensor type.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 2)) { return; } const TensorShapeProto& data_shape = ctx.getInputType(0)->tensor_type().shape(); const TensorShapeProto& indices_shape = ctx.getInputType(1)->tensor_type().shape(); int r = data_shape.dim_size(); if (r < 1) { fail_shape_inference("data tensor must have rank >= 1"); } int q = indices_shape.dim_size(); int axis = static_cast(getAttribute(ctx, "axis", 0)); if (axis < -r || axis >= r) { fail_shape_inference("axis must be in [-r, r-1]"); } if (axis < 0) { axis += r; } int out_rank = q + r - 1; if (out_rank == 0) { ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); } for (int i = 0; i < out_rank; ++i) { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = (i < axis) ? data_shape.dim(i) : // i < axis < r (i >= axis && i < axis + q) ? indices_shape.dim(i - axis) : // i - axis < q data_shape.dim(i - q + 1); // i < out_rank < q + r - 1 } })); static const char* GatherElements_ver11_doc = R"DOC( GatherElements takes two inputs `data` and `indices` of the same rank r >= 1 and an optional attribute `axis` that identifies an axis of `data` (by default, the outer-most axis, that is axis 0). It is an indexing operation that produces its output by indexing into the input data tensor at index positions determined by elements of the `indices` tensor. Its output shape is the same as the shape of `indices` and consists of one value (gathered from the `data`) for each element in `indices`. For instance, in the 3-D case (r = 3), the output produced is determined by the following equations: ``` out[i][j][k] = input[index[i][j][k]][j][k] if axis = 0, out[i][j][k] = input[i][index[i][j][k]][k] if axis = 1, out[i][j][k] = input[i][j][index[i][j][k]] if axis = 2, ``` This operator is also the inverse of ScatterElements. It is similar to Torch's gather operation. Example 1: ``` data = [ [1, 2], [3, 4], ] indices = [ [0, 0], [1, 0], ] axis = 1 output = [ [ [1, 1], [4, 3], ], ] ``` Example 2: ``` data = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ] indices = [ [1, 2, 0], [2, 0, 0], ] axis = 0 output = [ [ [4, 8, 3], [7, 2, 3], ], ] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( GatherElements, 11, OpSchema() .SetDoc(GatherElements_ver11_doc) .Attr( "axis", "Which axis to gather on. Negative value means " "counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of int32/int64 indices, with the same rank r as the input. All index values are expected to be " "within bounds [-s, s-1] along axis of size s. It is an error if any of the index values are out of bounds.", "Tind") .Output(0, "output", "Tensor of the same shape as indices.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to any tensor type.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain indices to integer types") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); // propagate indices' shape to output if it exists if (hasInputShape(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 1, 0); } })); static const char* Squeeze_ver11_doc = R"DOC( Remove single-dimensional entries from the shape of a tensor. Takes a parameter `axes` with a list of axes to squeeze. If `axes` is not provided, all the single dimensions will be removed from the shape. If an axis is selected with shape entry not equal to one, an error is raised. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Squeeze, 11, OpSchema() .Attr( "axes", "List of integers indicating the dimensions to squeeze. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(data).", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Squeeze_ver11_doc) .Input(0, "data", "Tensors with at least max(dims) dimensions.", "T") .Output(0, "squeezed", "Reshaped tensor with same data as input.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } std::vector axes; if (!getRepeatedAttribute(ctx, "axes", axes)) { return; } if (!ctx.getInputType(0)->tensor_type().has_shape()) { return; } ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_ndim = input_shape.dim_size(); std::transform( axes.begin(), axes.end(), axes.begin(), [&](int64_t axis) -> int64_t { return axis < 0 ? axis + input_ndim : axis; }); for (int i = 0, j = 0; i < input_ndim; ++i) { if (std::find(axes.begin(), axes.end(), i) != axes.end()) { if (input_shape.dim(i).has_dim_value() && input_shape.dim(i).dim_value() != 1) { fail_shape_inference( "Dimension of input ", i, " must be 1 instead of ", input_shape.dim(i).dim_value()); } ++j; } else { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = input_shape.dim(i); } } })); static const char* Unsqueeze_ver11_doc = R"DOC( Insert single-dimensional entries to the shape of an input tensor (`data`). Takes one required argument `axes` - which contains a list of dimension indices and this operator will insert a dimension of value `1` into the corresponding index of the output tensor (`expanded`). For example: Given an input tensor (`data`) of shape [3, 4, 5], then Unsqueeze(data, axes=[0, 4]) outputs a tensor (`expanded`) containing same data as `data` but with shape [1, 3, 4, 5, 1]. The attribute `axes` should not contain any duplicate entries. It is an error if it contains duplicates. The rank of the output tensor (`output_rank`) is the rank of the input tensor (`data`) plus the number of values in `axes`. Each value in `axes` should be within the (inclusive) range [-output_rank , output_rank - 1]. The order of values in `axes` does not matter and can come in any order. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Unsqueeze, 11, OpSchema() .Attr( "axes", "List of integers indicating the dimensions to be inserted. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(expanded).", AttributeProto::INTS) .SetDoc(Unsqueeze_ver11_doc) .Input(0, "data", "Original tensor", "T") .Output(0, "expanded", "Reshaped tensor with same data as input.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } std::vector axes; if (!getRepeatedAttribute(ctx, "axes", axes)) { return; } // validate 'axes' for duplicate entries std::unordered_set unique_values; for (const auto val : axes) { if (unique_values.find(val) != unique_values.end()) { fail_shape_inference( "'axes' attribute must not contain any duplicates"); } unique_values.insert(val); } if (!ctx.getInputType(0)->tensor_type().has_shape()) { return; } ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_ndim = input_shape.dim_size(); const auto output_ndim = input_ndim + static_cast(axes.size()); for (auto& axe : axes) { if (axe < -output_ndim || axe >= output_ndim) { fail_shape_inference( "values in 'axes' are beyond the bounds of the computed output shape"); } if (axe < 0) { axe += output_ndim; } } // sort after correcting negative axes values (if any) in the previous // step std::sort(axes.begin(), axes.end()); int j = 0; for (int i = 0; i < input_ndim; ++i) { while (static_cast(j) < axes.size() && axes[j] == ctx.getOutputType(0)->tensor_type().shape().dim_size()) { ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() ->set_dim_value(1); ++j; } *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = ctx.getInputType(0)->tensor_type().shape().dim(i); } while (static_cast(j) < axes.size() && axes[j] == ctx.getOutputType(0)->tensor_type().shape().dim_size()) { ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() ->set_dim_value(1); ++j; } })); static const char* SpaceToDepth_ver1_doc = R"DOC(SpaceToDepth rearranges blocks of spatial data into depth. More specifically, this op outputs a copy of the input tensor where values from the height and width dimensions are moved to the depth dimension. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SpaceToDepth, 1, OpSchema() .Attr( "blocksize", "Blocks of [blocksize, blocksize] are moved.", AttributeProto::INT) .SetDoc(SpaceToDepth_ver1_doc) .Input( 0, "input", "Input tensor of [N,C,H,W], where N is the batch axis, C is the channel or depth" ", H is the height and W is the width.", "T") .Output( 0, "output", "Output tensor of [N, C * blocksize * blocksize, H/blocksize, W/blocksize].", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); auto blocksize = getAttribute(ctx, "blocksize", 0); if (blocksize <= 0) fail_shape_inference("Blocksize must be positive"); if (hasInputShape(ctx, 0)) { auto& input_shape = getInputShape(ctx, 0); if (input_shape.dim_size() == 4) { // TODO: Clarify what behavior should be if H or W is not a // multiple of blocksize. updateOutputShape( ctx, 0, {input_shape.dim(0), input_shape.dim(1) * (blocksize * blocksize), input_shape.dim(2) / blocksize, input_shape.dim(3) / blocksize}); } else fail_shape_inference("Input tensor must be 4-dimensional"); } })); static const char* DepthToSpace_ver11_doc = R"DOC(DepthToSpace rearranges (permutes) data from depth into blocks of spatial data. This is the reverse transformation of SpaceToDepth. More specifically, this op outputs a copy of the input tensor where values from the depth dimension are moved in spatial blocks to the height and width dimensions. By default, `mode` = `DCR`. In the DCR mode, elements along the depth dimension from the input tensor are rearranged in the following order: depth, column, and then row. The output y is computed from the input x as below: b, c, h, w = x.shape tmp = np.reshape(x, [b, blocksize, blocksize, c // (blocksize**2), h, w]) tmp = np.transpose(tmp, [0, 3, 4, 1, 5, 2]) y = np.reshape(tmp, [b, c // (blocksize**2), h * blocksize, w * blocksize]) In the CRD mode, elements along the depth dimension from the input tensor are rearranged in the following order: column, row, and the depth. The output y is computed from the input x as below: b, c, h, w = x.shape tmp = np.reshape(x, [b, c // (blocksize ** 2), blocksize, blocksize, h, w]) tmp = np.transpose(tmp, [0, 1, 4, 2, 5, 3]) y = np.reshape(tmp, [b, c // (blocksize ** 2), h * blocksize, w * blocksize]) )DOC"; ONNX_OPERATOR_SET_SCHEMA( DepthToSpace, 11, OpSchema() .Attr( "blocksize", "Blocks of [blocksize, blocksize] are moved.", AttributeProto::INT) .Attr( "mode", "DCR (default) for depth-column-row order re-arrangement. Use CRD for column-row-depth order.", AttributeProto::STRING, std::string("DCR")) .SetDoc(DepthToSpace_ver11_doc) .Input( 0, "input", "Input tensor of [N,C,H,W], where N is the batch axis, C is the channel or depth" ", H is the height and W is the width.", "T") .Output( 0, "output", "Output tensor of [N, C/(blocksize * blocksize), H * blocksize, W * blocksize].", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); auto blocksize = getAttribute(ctx, "blocksize", 0); if (blocksize <= 0) fail_shape_inference("Blocksize must be positive"); if (hasInputShape(ctx, 0)) { auto& input_shape = getInputShape(ctx, 0); if (input_shape.dim_size() == 4) { // TODO: Clarify what behavior should be if C is not a multiple of // blocksize*blocksize. updateOutputShape( ctx, 0, {input_shape.dim(0), input_shape.dim(1) / (blocksize * blocksize), input_shape.dim(2) * blocksize, input_shape.dim(3) * blocksize}); } else fail_shape_inference("Input tensor must be 4-dimensional"); } })); static const char* Tile_ver6_doc = R"DOC(Constructs a tensor by tiling a given tensor. This is the same as function `tile` in Numpy, but no broadcast. For example A = [[1, 2], [3, 4]], B = [1, 2], tile(A, B) = [[1, 2, 1, 2], [3, 4, 3, 4]] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Tile, 6, OpSchema() .SetDoc(Tile_ver6_doc) .Input(0, "input", "Input tensor of any shape.", "T") .Input( 1, "repeats", "1D int64 tensor of the same length as input's dimension number, " "includes numbers of repeated copies along input's dimensions.", "T1") .Output( 0, "output", "Output tensor of the same dimension and type as tensor input. " "output_dim[i] = input_dim[i] * repeats[i]", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeConstraint( "T1", {"tensor(int64)"}, "Constrain repeat's type to int64 tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference // Needs atleast the first input to proceed if (!hasNInputShapes(ctx, 1)) { return; } const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_rank = input_shape.dim_size(); const auto* repeats_inputs = ctx.getInputData(1); auto* output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); if (nullptr != repeats_inputs && hasNInputShapes(ctx, 2)) { // shape inference is possible only when 'repeats' is an initializer const auto& repeats_shape = ctx.getInputType(1)->tensor_type().shape(); if (repeats_shape.dim_size() != 1 || repeats_inputs->data_type() != TensorProto::INT64) fail_shape_inference( "'Repeats' input must be 1D tensor of type int64"); const auto& repeats_data = ParseData(repeats_inputs); if (repeats_data.size() != static_cast(input_rank)) fail_shape_inference( "'Repeats' input has incorrect number of values. " "The number of values in 'repeats' must be equal " "to the number of input dimensions."); for (size_t i = 0; (int64_t)i < input_rank; ++i) { const auto& input_dim = input_shape.dim((int)i); auto* output_dim = output_shape->add_dim(); if (input_dim.has_dim_value()) { output_dim->set_dim_value( input_dim.dim_value() * repeats_data[i]); } } } else { // Infer output shape's rank in any case (if repeats data is not // available) auto* output_shape_0 = getOutputShape(ctx, 0); for (size_t i = 0; (int64_t)i < input_rank; ++i) { output_shape_0->add_dim(); } } return; })); static const char* Upsample_ver10_doc = R"DOC( Upsample the input tensor. Each dimension value of the output tensor is: output_dimension = floor(input_dimension * scale). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Upsample, 10, OpSchema() .Deprecate() .Attr( "mode", "Two interpolation modes: nearest (default), and linear (including bilinear, trilinear, etc)", AttributeProto::STRING, std::string("nearest")) .Input(0, "X", "N-D tensor", "T") .Input( 1, "scales", "The scale array along each dimension. It takes value greater than or equal to 1." " The number of elements of 'scales' should be the same as the rank of input 'X'.", "tensor(float)") .Output(0, "Y", "N-D tensor after resizing", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input 'X' and output 'Y' to all tensor types.") .SetDoc(Upsample_ver10_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { resizeShapeInference_opset7_to_10(ctx); })); static const char* Resize_ver11_doc = R"DOC( Resize the input tensor. In general, it calculates every value in the output tensor as a weighted average of neighborhood (a.k.a. sampling locations) in the input tensor. Each dimension value of the output tensor is: output_dimension = floor(input_dimension * (roi_end - roi_start) * scale) if input \"sizes\" is not specified. )DOC"; static const char* Resize_attr_coordinate_transformation_mode_doc = R"DOC( This attribute describes how to transform the coordinate in the resized tensor to the coordinate in the original tensor.
The coordinate of each dimension is transformed individually. Let's describe a case using axis x as an example. Denote x_resized as the coordinate of axis x in the resized tensor, x_original as the coordinate of axis x in the original tensor, length_original as the length of the original tensor in axis x, length_resized as the length of the resized tensor in axis x, roi_x = (start_x, end_x) of the axis x in input "roi", scale = length_resized / length_original,
if coordinate_transformation_mode is "half_pixel",
x_original = (x_resized + 0.5) / scale - 0.5,
if coordinate_transformation_mode is "pytorch_half_pixel",
x_original = length_resized > 1 ? (x_resized + 0.5) / scale - 0.5 : 0,
if coordinate_transformation_mode is "align_corners",
x_original = x_resized * (length_original - 1) / (length_resized - 1),
if coordinate_transformation_mode is "asymmetric",
x_original = x_resized / scale,
if coordinate_transformation_mode is "tf_half_pixel_for_nn",
x_original = (x_resized + 0.5) / scale,
if coordinate_transformation_mode is "tf_crop_and_resize",
x_original = length_resized > 1 ? start_x * (length_original - 1) + x_resized * (end_x - start_x) * (length_original - 1) / (length_resized - 1) : 0.5 * (start_x + end_x) * (length_original - 1).)DOC"; ONNX_OPERATOR_SET_SCHEMA( Resize, 11, OpSchema() .Attr( "mode", "Three interpolation modes: nearest (default), linear and cubic. " "The \"linear\" mode includes linear interpolation for 1D tensor and N-linear interpolation for N-D tensor (for example, bilinear interpolation for 2D tensor). " "The \"cubic\" mode includes cubic interpolation for 1D tensor and N-cubic interpolation for N-D tensor (for example, bicubic interpolation for 2D tensor).", AttributeProto::STRING, std::string("nearest")) .Attr( "cubic_coeff_a", "The coefficient 'a' used in cubic interpolation. Two common choice are -0.5 (in some cases of TensorFlow) and -0.75" " (in PyTorch). Check out Equation (4) in https://ieeexplore.ieee.org/document/1163711 for the details. " "This attribute is valid only if \"mode\" is \"cubic\".", AttributeProto::FLOAT, static_cast(-0.75)) .Attr( "exclude_outside", "If set to 1, the weight of sampling locations outside the tensor will be set to 0" " and the weight will be renormalized so that their sum is 1.0. The default value is 0.", AttributeProto::INT, static_cast(0)) .Attr( "coordinate_transformation_mode", Resize_attr_coordinate_transformation_mode_doc, AttributeProto::STRING, std::string("half_pixel")) .Attr( "nearest_mode", "Four modes: round_prefer_floor (default, as known as round half down), round_prefer_ceil (as known as round half up), floor, ceil. Only used by nearest interpolation. It indicates how to get \"nearest\" pixel in input tensor from x_original, so this attribute is valid only if \"mode\" is \"nearest\".", AttributeProto::STRING, std::string("round_prefer_floor")) .Attr( "extrapolation_value", "When coordinate_transformation_mode is \"tf_crop_and_resize\" and x_original is outside the range [0, length_original - 1], this value is used as the corresponding output value. Default is 0.0f.", AttributeProto::FLOAT, static_cast(0)) .Input(0, "X", "N-D tensor", "T1") .Input( 1, "roi", "1-D tensor given as [start1, ..., startN, end1, ..., endN], where N is the rank of X. The RoIs' coordinates are normalized in the coordinate system of the input image. It only takes effect when coordinate_transformation_mode is \"tf_crop_and_resize\"", "T2") .Input( 2, "scales", "The scale array along each dimension. It takes value greater than 0. If it's less than 1," " it's sampling down, otherwise, it's upsampling. The number of elements of 'scales' should" " be the same as the rank of input 'X'. Only one of 'scales' and 'sizes' can be specified. If 'size' is needed, the user can use an empty string as the name of 'scales' in this operator's input list.", "tensor(float)") .Input( 3, "sizes", "The size of the output tensor. The number of elements of 'sizes' should be the same as the" " rank of input 'X'. Only one of 'scales' and 'sizes' can be specified.", "tensor(int64)", OpSchema::Optional) .Output(0, "Y", "N-D tensor after resizing", "T1") .TypeConstraint( "T1", OpSchema::all_tensor_types(), "Constrain input 'X' and output 'Y' to all tensor types.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain roi type to float or double.") .SetDoc(Resize_ver11_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { resizeShapeInference(ctx, true); })); ONNX_OPERATOR_SET_SCHEMA( Identity, 1, OpSchema() .SetDoc("Identity operator") .Input(0, "input", "Input tensor", "T") .Output(0, "output", "Tensor to copy input into.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Compress_ver11_doc = R"DOC( Selects slices from an input tensor along a given axis where condition evaluates to True for each axis index. In case axis is not provided, input is flattened before elements are selected. Compress behaves like numpy.compress: https://docs.scipy.org/doc/numpy/reference/generated/numpy.compress.html )DOC"; ONNX_OPERATOR_SET_SCHEMA( Compress, 11, OpSchema() .SetDoc(Compress_ver11_doc) .Attr( "axis", "(Optional) Axis along which to take slices. If not specified, " "input is flattened before elements being selected. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(input).", AttributeProto::INT, OPTIONAL_VALUE) .Input(0, "input", "Tensor of rank r >= 1.", "T") .Input( 1, "condition", "Rank 1 tensor of booleans to indicate which slices or data elements to be selected. " "Its length can be less than the input length along the axis " "or the flattened input size if axis is not specified. " "In such cases data slices or elements exceeding the condition length are discarded.", "T1") .Output( 0, "output", "Tensor of rank r if axis is specified. Otherwise output is a Tensor of rank 1.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains to boolean tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasInputShape(ctx, 0)) { const TensorShapeProto& indices_shape = ctx.getInputType(0)->tensor_type().shape(); int r = indices_shape.dim_size(); if (r < 1) { fail_shape_inference("Indices tensor must have rank >= 1"); } auto axisAttr = ctx.getAttribute("axis"); if (axisAttr) { int axis = static_cast(axisAttr->i()); if (axis < -r || axis >= r) { fail_shape_inference( "'axis' must be in [-rank(indices), rank(indices)-1]"); } if (axis < 0) { axis += r; } } } })); static const char* OneHot_ver11_doc = R"DOC( Produces a one-hot tensor based on inputs. The locations represented by the index values in the 'indices' input tensor will have 'on_value' and the other locations will have 'off_value' in the output tensor, where 'on_value' and 'off_value' are specified as part of required input argument 'values', which is a two-element tensor of format [off_value, on_value]. The rank of the output tensor will be one greater than the rank of the input tensor. The additional dimension is for one-hot representation. The additional dimension will be inserted at the position specified by 'axis'. If 'axis' is not specified then then additional dimension will be inserted as the innermost dimension, i.e. axis=-1. The size of the additional dimension is specified by required scalar input 'depth'. The type of the output tensor is the same as the type of the 'values' input. Any entries in the 'indices' input tensor with values outside the range [-depth, depth-1] will result in one-hot representation with all 'off_value' values in the output tensor. when axis = 0: output[input[i, j, k], i, j, k] = 1 for all i, j, k and 0 otherwise. when axis = -1: output[i, j, k, input[i, j, k]] = 1 for all i, j, k and 0 otherwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( OneHot, 11, OpSchema() .SetDoc(OneHot_ver11_doc) .Attr( "axis", "(Optional) Axis along which one-hot representation in added. Default: axis=-1. " "axis=-1 means that the additional dimension will be inserted as the " "innermost/last dimension in the output tensor. Negative value means counting dimensions " "from the back. Accepted range is [-r-1, r] where r = rank(indices).", AttributeProto::INT, static_cast(-1)) .Input( 0, "indices", "Input tensor containing indices. Any entries in the 'indices' input tensor with " "values outside the range [-depth, depth-1] will result in one-hot representation with all " "'off_value' values in the output tensor." "In case 'indices' is of non-integer type, the values will be casted to int64 before use.", "T1") .Input( 1, "depth", "Scalar specifying the number of classes in one-hot tensor. This is also the size " "of the one-hot dimension (specified by 'axis' attribute) added on in the output " "tensor. The values in the 'indices' input tensor are expected to be " "in the range [-depth, depth-1]. " "In case 'depth' is of non-integer type, it will be casted to int64 before use.", "T2") .Input( 2, "values", "Rank 1 tensor containing exactly two elements, in the format [off_value, on_value], " "where 'on_value' is the value used for filling locations specified in 'indices' input " "tensor, and 'off_value' is the value used for filling locations other than those specified " "in 'indices' input tensor. ", "T3") .Output( 0, "output", "Tensor of rank one greater than input tensor 'indices', i.e. rank(output) = rank(indices) + 1. " "The data type for the elements of the output tensor is the same as the type of input 'values' " "is used.", "T3") .TypeConstraint( "T1", OpSchema::all_numeric_types(), "Constrains input to only numeric types.") .TypeConstraint( "T2", OpSchema::all_numeric_types(), "Constrains input to only numeric types.") .TypeConstraint( "T3", OpSchema::all_tensor_types(), "Constrain to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Check that the node has three inputs. if (ctx.getNumInputs() != 3) { fail_type_inference("OneHot node must have three inputs."); } // Input 'depth' must be a scalar or a single-element vector. // TODO: Ideally to match spec for this input only Scalar should // be allowed. Making this change now can affect backward // compatibility for this op. Since this does not seem like a good // justification to update version for this op, allowing both scalar // and 1 element vector for now. In future when version update for // this op is done we should only allow scalar or chage the spec to // allow both. if (hasInputShape(ctx, 1)) { auto& depth_shape = getInputShape(ctx, 1); if (depth_shape.dim_size() != 0 && depth_shape.dim_size() != 1) { fail_type_inference( "Input 'depth' must be a scalar or rank 1 tensor."); } if (depth_shape.dim_size() == 1 && depth_shape.dim((int)0).has_dim_value() && depth_shape.dim((int)0).dim_value() != 1) { fail_type_inference( "Input 'depth' must have exactly one element."); } } // Input 'values' must be a two-element vector. if (hasInputShape(ctx, 2)) { auto& values_shape = getInputShape(ctx, 2); if (values_shape.dim_size() != 1) { fail_type_inference("Input 'values' must be rank 1 tensor."); } if (values_shape.dim((int)0).has_dim_value() && values_shape.dim((int)0).dim_value() != 2) { fail_type_inference( "Input 'values' must have exactly two elements."); } } // Set output type to be the same as the third input, 'values'. propagateElemTypeFromInputToOutput(ctx, 2, 0); // Set the output shape, if input 0 (indices) shape is available. if (hasInputShape(ctx, 0)) { const TensorShapeProto& indices_shape = ctx.getInputType(0)->tensor_type().shape(); int r = indices_shape.dim_size(); if (r < 1) { fail_shape_inference("Indices tensor must have rank >= 1"); } int out_rank = r + 1; int axis = static_cast(getAttribute(ctx, "axis", -1)); if (axis < -out_rank || axis >= out_rank) { fail_shape_inference( "'axis' must be in [-rank(indices), rank(indices)-1]"); } if (axis < 0) { axis += out_rank; } auto* output_shape = getOutputShape(ctx, 0); for (int i = 0; i < out_rank; ++i) { auto* dim = output_shape->add_dim(); if (i < axis) { if (indices_shape.dim(i).has_dim_value()) { dim->set_dim_value(indices_shape.dim(i).dim_value()); } else if (indices_shape.dim(i).has_dim_param()) { dim->set_dim_param(indices_shape.dim(i).dim_param()); } } else if (i > axis) { if (indices_shape.dim(i - 1).has_dim_value()) { dim->set_dim_value(indices_shape.dim(i - 1).dim_value()); } else if (indices_shape.dim(i - 1).has_dim_param()) { dim->set_dim_param(indices_shape.dim(i - 1).dim_param()); } } } } })); ONNX_OPERATOR_SET_SCHEMA( IsNaN, 9, OpSchema() .SetDoc(R"DOC(Returns which elements of the input are NaN.)DOC") .Input(0, "X", "input", "T1") .Output(0, "Y", "output", "T2") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input types to float tensors.") .TypeConstraint( "T2", {"tensor(bool)"}, "Constrain output types to boolean tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { updateOutputElemType(ctx, 0, TensorProto::BOOL); if (hasInputShape(ctx, 0)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); ONNX_OPERATOR_SET_SCHEMA( IsInf, 10, OpSchema() .SetDoc(R"DOC(Map infinity to true and other values to false.)DOC") .Input(0, "X", "input", "T1") .Output(0, "Y", "output", "T2") .Attr( "detect_positive", "(Optional) Whether map positive infinity to true. Default to 1 " "so that positive infinity induces true. Set this attribute to 0 " "if positive infinity should be mapped to false.", AttributeProto::INT, static_cast(1)) .Attr( "detect_negative", "(Optional) Whether map negative infinity to true. Default to 1 " "so that negative infinity induces true. Set this attribute to 0 " "if negative infinity should be mapped to false.", AttributeProto::INT, static_cast(1)) .TypeConstraint( "T1", {"tensor(float)", "tensor(double)"}, "Constrain input types to float tensors.") .TypeConstraint( "T2", {"tensor(bool)"}, "Constrain output types to boolean tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { updateOutputElemType(ctx, 0, TensorProto::BOOL); if (hasInputShape(ctx, 0)) { propagateShapeFromInputToOutput(ctx, 0, 0); } })); static const char* Where_ver9_doc = R"DOC( Return elements, either from X or Y, depending on condition (with Numpy-style broadcasting support). Where behaves like numpy.where with three parameters: https://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html )DOC"; ONNX_OPERATOR_SET_SCHEMA( Where, 9, OpSchema() .SetDoc(Where_ver9_doc) .Input( 0, "condition", "When True (nonzero), yield X, otherwise yield Y", "B") .Input( 1, "X", "values selected at indices where condition is True", "T") .Input( 2, "Y", "values selected at indices where condition is False", "T") .Output( 0, "output", "Tensor of shape equal to the broadcasted shape of condition, X, and Y.", "T") .TypeConstraint("B", {"tensor(bool)"}, "Constrain to boolean tensors.") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 1, 0); if (hasNInputShapes(ctx, 3)) { std::vector shapes; shapes.push_back(&ctx.getInputType(0)->tensor_type().shape()); shapes.push_back(&ctx.getInputType(1)->tensor_type().shape()); shapes.push_back(&ctx.getInputType(2)->tensor_type().shape()); multidirectionalBroadcastShapeInference( shapes, *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); } })); static const char* NonZero_ver9_doc = R"DOC( Returns the indices of the elements that are non-zero (in row-major order - by dimension). NonZero behaves similar to numpy.nonzero: https://docs.scipy.org/doc/numpy/reference/generated/numpy.nonzero.html )DOC"; ONNX_OPERATOR_SET_SCHEMA( NonZero, 9, OpSchema() .SetDoc(NonZero_ver9_doc) .Input(0, "X", "input", "T") .Output(0, "Y", "output", "tensor(int64)") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { updateOutputElemType(ctx, 0, TensorProto::INT64); })); static const char* ReverseSequence_ver10_doc = R"DOC( Reverse batch of sequences having different lengths specified by `sequence_lens`. For each slice i iterating on batch axis, the operator reverses the first sequence_lens[i] elements on time axis, and copies elements whose index's beyond sequence_lens[i] to the output. So the output slice i contains reversed sequences on the first sequence_lens[i] elements, then have original values copied for the other elements. Example 1: input = [[0.0, 4.0, 8.0, 12.0], [1.0, 5.0, 9.0, 13.0], [2.0, 6.0, 10.0, 14.0], [3.0, 7.0, 11.0, 15.0]] sequence_lens = [4, 3, 2, 1] time_axis = 0 batch_axis = 1 output = [[3.0, 6.0, 9.0, 12.0], [2.0, 5.0, 8.0, 13.0], [1.0, 4.0, 10.0, 14.0], [0.0, 7.0, 11.0, 15.0]] Example 2: input = [[0.0, 1.0, 2.0, 3.0 ], [4.0, 5.0, 6.0, 7.0 ], [8.0, 9.0, 10.0, 11.0], [12.0, 13.0, 14.0, 15.0]] sequence_lens = [1, 2, 3, 4] time_axis = 1 batch_axis = 0 output = [[0.0, 1.0, 2.0, 3.0 ], [5.0, 4.0, 6.0, 7.0 ], [10.0, 9.0, 8.0, 11.0], [15.0, 14.0, 13.0, 12.0]] )DOC"; ONNX_OPERATOR_SET_SCHEMA( ReverseSequence, 10, OpSchema() .SetDoc(ReverseSequence_ver10_doc) .Attr( "time_axis", "(Optional) Specify which axis is time axis. Must be one of 0 (default), or 1.", AttributeProto::INT, static_cast(0)) .Attr( "batch_axis", "(Optional) Specify which axis is batch axis. Must be one of 1 (default), or 0.", AttributeProto::INT, static_cast(1)) .Input(0, "input", "Tensor of rank r >= 2.", "T") .Input( 1, "sequence_lens", "Tensor specifying lengths of the sequences in a batch. It has shape `[batch_size]`.", "tensor(int64)") .Output(0, "Y", "Tensor with same shape of input.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input and output types can be of any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 2)) { return; } auto& first_input_shape = getInputShape(ctx, 0); if (first_input_shape.dim_size() < 2) { fail_shape_inference("'input' must have rank >= 2"); } auto& seq_len_input_shape = getInputShape(ctx, 1); if (seq_len_input_shape.dim_size() != 1) { fail_shape_inference("'sequence_lens' must have rank of 1"); } propagateShapeFromInputToOutput(ctx, 0, 0); })); static const char* Unique_ver11_doc = R"DOC( Find the unique elements of a tensor. When an optional attribute 'axis' is provided, unique subtensors sliced along the 'axis' are returned. Otherwise the input tensor is flattened and unique values of the flattened tensor are returned. This operator returns the unique values or sliced unique subtensors of the input tensor and three optional outputs. The first output tensor 'Y' contains all unique values or subtensors of the input. The second optional output tensor 'indices' contains indices of 'Y' elements' first occurance in 'X'.. The third optional output tensor 'inverse_indices' contains, for elements of 'X', its corresponding indices in 'Y'. ". The fourth optional output tensor 'counts' contains the count of each element of 'Y' in the input. Outputs are either sorted in ascending order or optionally in the order of the first occurrence of the values in the input. https://docs.scipy.org/doc/numpy/reference/generated/numpy.unique.html Example 1: input_X = [2, 1, 1, 3, 4, 3] attribute_sorted = 0 attribute_axis = None output_Y = [2, 1, 3, 4] output_indices = [0, 1, 3, 4] output_inverse_indices = [0, 1, 1, 2, 3, 2] output_counts = [1, 2, 2, 1] Example 2: input_X = [[1, 3], [2, 3]] attribute_sorted = 1 attribute_axis = None output_Y = [1, 2, 3] output_indices = [0, 2, 1] output_inverse_indices = [0, 2, 1, 2] output_counts = [1, 1, 2] Example 3: input_X = [[1, 0, 0], [1, 0, 0], [2, 3, 4]] attribute_sorted = 1 attribute_axis = 0 output_Y = [[1, 0, 0], [2, 3, 4]] output_indices = [0, 2] output_inverse_indices = [0, 0, 1] output_counts = [2, 1] Example 4: input_x = [[[1., 1.], [0., 1.], [2., 1.], [0., 1.]], [[1., 1.], [0., 1.], [2., 1.], [0., 1.]]] attribute_sorted = 1 attribute_axis = 1 intermediate data are presented below for better understanding: there are 4 subtensors sliced along axis 1 of input_x (shape = (2, 4, 2)): A: [[1, 1], [1, 1]], [[0, 1], [0, 1]], [[2, 1], [2, 1]], [[0, 1], [0, 1]]. there are 3 unique subtensors: [[1, 1], [1, 1]], [[0, 1], [0, 1]], [[2, 1], [2, 1]]. sorted unique subtensors: B: [[0, 1], [0, 1]], [[1, 1], [1, 1]], [[2, 1], [2, 1]]. output_Y is constructed from B: [[[0. 1.], [1. 1.], [2. 1.]], [[0. 1.], [1. 1.], [2. 1.]]] output_indices is to map from B to A: [1, 0, 2] output_inverse_indices is to map from A to B: [1, 0, 2, 0] output_counts = [2 1 1] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Unique, 11, OpSchema() .SetDoc(Unique_ver11_doc) .Attr( "sorted", "(Optional) Whether to sort the unique elements in ascending order before returning as output. " "Must be one of 0, or 1 (default).", AttributeProto::INT, static_cast(1)) .Attr( "axis", "(Optional) The dimension to apply unique. If not specified, the unique elements of the " "flattened input are returned. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(input).", AttributeProto::INT, OPTIONAL_VALUE) .Input(0, "X", "A N-D input tensor that is to be processed.", "T") .Output( 0, "Y", "A tensor of the same type as 'X' " "containing all the unique values or subtensors sliced along a provided 'axis' in 'X', either sorted " "or maintained in the same order they occur in input 'X'", "T") .Output( 1, "indices", "A 1-D INT64 tensor " "containing indices of 'Y' elements' first occurance in 'X'. " "When 'axis' is provided, it contains indices to subtensors in input 'X' on the 'axis'. " "When 'axis' is not provided, it contains indices to values in the flattened input tensor. ", "tensor(int64)", OpSchema::Optional) .Output( 2, "inverse_indices", "A 1-D INT64 tensor " "containing, for elements of 'X', its corresponding indices in 'Y'. " "When 'axis' is provided, it contains indices to subtensors in output 'Y' on the 'axis'. " "When 'axis' is not provided, it contains indices to values in output 'Y'. ", "tensor(int64)", OpSchema::Optional) .Output( 3, "counts", "A 1-D INT64 tensor containing " "the count of each element " "of 'Y' in input 'X'", "tensor(int64)", OpSchema::Optional) .TypeConstraint( "T", OpSchema::all_tensor_types(), "Input can be of any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); const TypeProto* xTensorProto = ctx.getInputType(0); TypeProto* yTensorProto = ctx.getOutputType(0); TypeProto* indicesTensorProto = nullptr; TypeProto* inverseIndicesTensorProto = nullptr; TypeProto* countsTensorProto = nullptr; // 'indices', 'inverse_indices', and 'counts' are 1-D tensors of // unknown dimension. auto num_outputs = ctx.getNumOutputs(); if (num_outputs >= 2) { indicesTensorProto = ctx.getOutputType(1); updateOutputElemType(ctx, 1, TensorProto::INT64); indicesTensorProto->mutable_tensor_type() ->mutable_shape() ->add_dim(); } if (num_outputs >= 3) { inverseIndicesTensorProto = ctx.getOutputType(2); updateOutputElemType(ctx, 2, TensorProto::INT64); inverseIndicesTensorProto->mutable_tensor_type() ->mutable_shape() ->add_dim(); } if (num_outputs >= 4) { countsTensorProto = ctx.getOutputType(3); updateOutputElemType(ctx, 3, TensorProto::INT64); countsTensorProto->mutable_tensor_type() ->mutable_shape() ->add_dim(); } auto axisAttr = ctx.getAttribute("axis"); if (!axisAttr) { // 'axis' is not provided. Input 'X' is flattened. // 'Y' is a 1-D tensor of unknown dimension. yTensorProto->mutable_tensor_type()->mutable_shape()->add_dim(); } else { // 'axis' is provided. int axis = static_cast(axisAttr->i()); const TensorShapeProto& input_shape = xTensorProto->tensor_type().shape(); int rank = input_shape.dim_size(); if (axis < 0) axis += rank; if (axis < 0 || axis >= rank) fail_shape_inference("Invalid value for attribute axis"); // 'Y' has the same shape as 'X' except in the 'axis' dimension // which is unknown. for (int i = 0; i < input_shape.dim_size(); i++) { auto* dim = yTensorProto->mutable_tensor_type() ->mutable_shape() ->add_dim(); if (i != axis) { *dim = input_shape.dim(i); } } } })); static const char* GatherND_ver12_doc = R"DOC( Given `data` tensor of rank `r` >= 1, `indices` tensor of rank `q` >= 1, and `batch_dims` integer `b`, this operator gathers slices of `data` into an output tensor of rank `q + r - indices_shape[-1] - 1 - b`. `indices` is an q-dimensional integer tensor, best thought of as a `(q-1)`-dimensional tensor of index-tuples into `data`, where each element defines a slice of `data` `batch_dims` (denoted as `b`) is an integer indicating the number of batch dimensions, i.e the leading `b` number of dimensions of `data` tensor and `indices` are representing the batches, and the gather starts from the `b+1` dimension. Some salient points about the inputs' rank and shape: 1) r >= 1 and q >= 1 are to be honored. There is no dependency condition to be met between ranks `r` and `q` 2) The first `b` dimensions of the shape of `indices` tensor and `data` tensor must be equal. 3) b < min(q, r) is to be honored. 4) The `indices_shape[-1]` should have a value between 1 (inclusive) and rank `r-b` (inclusive) 5) All values in `indices` are expected to be within bounds [-s, s-1] along axis of size `s` (i.e.) `-data_shape[i] <= indices[...,i] <= data_shape[i] - 1`. It is an error if any of the index values are out of bounds. The output is computed as follows: The output tensor is obtained by mapping each index-tuple in the `indices` tensor to the corresponding slice of the input `data`. 1) If `indices_shape[-1] > r-b` => error condition 2) If `indices_shape[-1] == r-b`, since the rank of `indices` is `q`, `indices` can be thought of as `N` `(q-b-1)`-dimensional tensors containing 1-D tensors of dimension `r-b`, where `N` is an integer equals to the product of 1 and all the elements in the batch dimensions of the indices_shape. Let us think of each such `r-b` ranked tensor as `indices_slice`. Each *scalar value* corresponding to `data[0:b-1,indices_slice]` is filled into the corresponding location of the `(q-b-1)`-dimensional tensor to form the `output` tensor (Example 1 below) 3) If `indices_shape[-1] < r-b`, since the rank of `indices` is `q`, `indices` can be thought of as `N` `(q-b-1)`-dimensional tensor containing 1-D tensors of dimension `< r-b`. Let us think of each such tensors as `indices_slice`. Each *tensor slice* corresponding to `data[0:b-1, indices_slice , :]` is filled into the corresponding location of the `(q-b-1)`-dimensional tensor to form the `output` tensor (Examples 2, 3, 4 and 5 below) This operator is the inverse of `ScatterND`. `Example 1` batch_dims = 0 data = [[0,1],[2,3]] # data_shape = [2, 2] indices = [[0,0],[1,1]] # indices_shape = [2, 2] output = [0,3] # output_shape = [2] `Example 2` batch_dims = 0 data = [[0,1],[2,3]] # data_shape = [2, 2] indices = [[1],[0]] # indices_shape = [2, 1] output = [[2,3],[0,1]] # output_shape = [2, 2] `Example 3` batch_dims = 0 data = [[[0,1],[2,3]],[[4,5],[6,7]]] # data_shape = [2, 2, 2] indices = [[0,1],[1,0]] # indices_shape = [2, 2] output = [[2,3],[4,5]] # output_shape = [2, 2] `Example 4` batch_dims = 0 data = [[[0,1],[2,3]],[[4,5],[6,7]]] # data_shape = [2, 2, 2] indices = [[[0,1]],[[1,0]]] # indices_shape = [2, 1, 2] output = [[[2,3]],[[4,5]]] # output_shape = [2, 1, 2] `Example 5` batch_dims = 1 data = [[[0,1],[2,3]],[[4,5],[6,7]]] # data_shape = [2, 2, 2] indices = [[1],[0]] # indices_shape = [2, 1] output = [[2,3],[4,5]] # output_shape = [2, 2] )DOC"; ONNX_OPERATOR_SET_SCHEMA( GatherND, 12, OpSchema() .SetDoc(GatherND_ver12_doc) .Attr( "batch_dims", "The number of batch dimensions. The gather of indexing starts from dimension of data[batch_dims:]", AttributeProto::INT, static_cast(0)) .Input(0, "data", "Tensor of rank r >= 1.", "T") .Input( 1, "indices", "Tensor of rank q >= 1. All index values are expected to be within bounds [-s, s-1] " "along axis of size s. It is an error if any of the index values are out of bounds.", "tensor(int64)") .Output( 0, "output", "Tensor of rank q + r - indices_shape[-1] - 1.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference if (!hasNInputShapes(ctx, 2)) { // cannot proceed with shape or rank inference return; } const auto& data_shape = ctx.getInputType(0)->tensor_type().shape(); const auto data_rank = data_shape.dim_size(); const auto& indices_shape = ctx.getInputType(1)->tensor_type().shape(); const auto indices_rank = indices_shape.dim_size(); int64_t batch_dims_data = getAttribute(ctx, "batch_dims", 0); if (data_rank < 1 || indices_rank < 1) { fail_shape_inference( "Both `data` and `indices` input tensors in GatherND op " "need to have rank larger than 0."); } // cannot ascertain if the input shapes are valid if shape of // `indices` is missing last dimension value so return at this point if (!indices_shape.dim(indices_rank - 1).has_dim_value()) { return; } const auto last_index_dimension = indices_shape.dim(indices_rank - 1).dim_value() + batch_dims_data; if (last_index_dimension > data_rank) { fail_shape_inference( "Last dimension of `indices` input tensor in GatherND op " "must not be larger than the rank of `data` tensor"); } for (int i = 0; i < indices_rank - 1; ++i) { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = indices_shape.dim(i); } for (int i = static_cast(last_index_dimension); i < data_rank; ++i) { *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = data_shape.dim(i); } })); static const char* Pad_ver11_doc = R"DOC( Given a tensor containing the data to be padded (`data`), a tensor containing the number of start and end pad values for axis (`pads`), (optionally) a `mode`, and (optionally) `constant_value`, a padded tensor (`output`) is generated. The three supported `modes` are (similar to corresponding modes supported by `numpy.pad`): 1) `constant`(default) - pads with a given constant value as specified by `constant_value` (which defaults to 0) 2) `reflect` - pads with the reflection of the vector mirrored on the first and last values of the vector along each axis 3) `edge` - pads with the edge values of array Example 1 (`constant` mode): Insert 0 pads to the beginning of the second dimension. data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] pads = [0, 2, 0, 0] mode = 'constant' constant_value = 0.0 output = [ [ [0.0, 0.0, 1.0, 1.2], [0.0, 0.0, 2.3, 3.4], [0.0, 0.0, 4.5, 5.7], ], ] Example 2 (`reflect` mode): data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] pads = [0, 2, 0, 0] mode = 'reflect' output = [ [ [1.0, 1.2, 1.0, 1.2], [2.3, 3.4, 2.3, 3.4], [4.5, 5.7, 4.5, 5.7], ], ] Example 3 (`edge` mode): data = [ [1.0, 1.2], [2.3, 3.4], [4.5, 5.7], ] pads = [0, 2, 0, 0] mode = 'edge' output = [ [ [1.0, 1.0, 1.0, 1.2], [2.3, 2.3, 2.3, 3.4], [4.5, 4.5, 4.5, 5.7], ], ] )DOC"; ONNX_OPERATOR_SET_SCHEMA( Pad, 11, OpSchema() .Attr( "mode", "Supported modes: `constant`(default), `reflect`, `edge`", AttributeProto::STRING, std::string("constant")) .SetDoc(Pad_ver11_doc) .Input(0, "data", "Input tensor.", "T") .Input( 1, "pads", "Tensor of integers indicating the number of padding elements to add or remove (if negative) " "at the beginning and end of each axis. For 2D input tensor, it is the number of pixels. " "`pads` should be a 1D tensor of shape [2 * input_rank]. " "`pads` format should be: [x1_begin, x2_begin,...,x1_end, x2_end,...], " "where xi_begin is the number of pad values added at the beginning of axis `i` and " "xi_end, the number of pad values added at the end of axis `i`.", "tensor(int64)") .Input( 2, "constant_value", "(Optional) A scalar value to be used if the mode chosen is `constant` (by default it is 0).", "T", OpSchema::Optional) .Output(0, "output", "Tensor after padding.", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrains input and output to only numeric types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference needs the input data shape if (!hasNInputShapes(ctx, 1)) { return; } const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_rank = input_shape.dim_size(); // Infer output shape if 'pads' tensor is available const auto* pads_initializer = ctx.getInputData(1); if (nullptr != pads_initializer) { if (pads_initializer->dims_size() != 1 || pads_initializer->data_type() != TensorProto::INT64) fail_shape_inference( "'pads' input must be a 1D (shape: [2 * input_rank]) tensor of type int64"); const auto& pads_data = ParseData(pads_initializer); if (pads_data.size() != static_cast(2 * input_rank)) fail_shape_inference("Pads has incorrect number of values"); auto* output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); for (size_t i = 0; static_cast(i) < input_rank; ++i) { const auto& input_dim = input_shape.dim((int)i); auto* output_dim = output_shape->add_dim(); if (input_dim.has_dim_value()) { output_dim->set_dim_value( input_dim.dim_value() + pads_data[i] + pads_data[i + input_rank]); } else if (pads_data[i] + pads_data[i + input_rank] == 0) { *output_dim = input_dim; } } } else { // Infer output shapes' rank in any case auto* output_shape_0 = getOutputShape(ctx, 0); for (size_t i = 0; static_cast(i) < input_rank; ++i) { output_shape_0->add_dim(); } } return; })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/tensor/utils.cc0000664000000000000000000001426513655345213016104 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/tensor/utils.h" namespace ONNX_NAMESPACE { void resizeShapeInferenceHelper( const TensorShapeProto& input_shape, const std::vector& sizes_data, TensorShapeProto* output_shape) { if (!sizes_data.empty()) { for (int i = 0; i < input_shape.dim_size(); ++i) { auto* dim = output_shape->mutable_dim(i); dim->set_dim_value(sizes_data[i]); } return; } } void resizeShapeInferenceHelper( const TensorShapeProto& input_shape, const std::vector& scales_data, TensorShapeProto* output_shape) { for (int i = 0; i < input_shape.dim_size(); ++i) { auto* dim = output_shape->mutable_dim(i); // If input_shape has dim_value, we caculate the scaled result // If input_shape doesn's have one, we leave it here if (input_shape.dim(i).has_dim_value()) { int64_t dim_value = static_cast(std::floor( static_cast(input_shape.dim(i).dim_value()) * scales_data[i])); // If output_shape has dim_value, we validate the caculated result // If output_shape doesn's have one, we set it to the scaled result if (dim->has_dim_value()) { if (static_cast(dim->dim_value()) != dim_value) { fail_shape_inference( "Dimension value inferred (", dim_value, ") is not equal to the existing dim value (", dim->dim_value(), ")."); } } else { dim->set_dim_value(static_cast(dim_value)); } // dim->has_dim_value() } // input_shape.dim(i).has_dim_value() } } void resizeShapeInference(InferenceContext& ctx, bool is_resize_op) { if (!hasNInputShapes(ctx, 1)) { return; } propagateElemTypeFromInputToOutput(ctx, 0, 0); const auto& input_shape = getInputShape(ctx, 0); auto* output_shape = getOutputShape(ctx, 0); const auto* scales = ctx.getInputData(is_resize_op ? 2 : 1); if (output_shape->dim_size() > 0) { if (output_shape->dim_size() != input_shape.dim_size()) { fail_shape_inference( "Ranks inferred (", input_shape.dim_size(), ") is not equal to the existing rank value (", output_shape->dim_size(), ")."); } } else { // Infer the rank of output anyway for (int i = 0; i < input_shape.dim_size(); ++i) { output_shape->add_dim(); } } if (is_resize_op && ctx.getNumInputs() == 4) { const auto* sizes = ctx.getInputData(3); if (nullptr != sizes) { if (sizes->data_type() == TensorProto::INT64) { const auto sizes_data = ParseData(sizes); if (sizes_data.size() != static_cast(input_shape.dim_size())) { fail_shape_inference( "Number of elements of input 'sizes' must be same as rank of input 'X'"); } resizeShapeInferenceHelper(input_shape, sizes_data, output_shape); } else { fail_shape_inference("Input 'sizes' must have int64 element type."); } } } else if (nullptr != scales) { // Infer output shape's dimension value if 'scales' is known. if (scales->data_type() == TensorProto::FLOAT) { const auto& scales_data = ParseData(scales); if (scales_data.size() != static_cast(input_shape.dim_size())) { fail_shape_inference( "Number of elements of input 'scales' must be same as rank of input 'X'"); } resizeShapeInferenceHelper(input_shape, scales_data, output_shape); } else { fail_shape_inference("Input 'scales' must have float element type."); } } // nullptr != scales } void resizeShapeInferenceHelper_opset7_to_10( const TensorShapeProto& input_shape, const std::vector& scales_data, TensorShapeProto* output_shape) { for (int i = 0; i < input_shape.dim_size(); ++i) { auto* dim = output_shape->mutable_dim(i); // If input_shape has dim_value, we caculate the scaled result // If input_shape doesn's have one, we leave it here if (input_shape.dim(i).has_dim_value()) { int64_t dim_value = static_cast(std::floor( static_cast(input_shape.dim(i).dim_value()) * scales_data[i])); // If output_shape has dim_value, we validate the caculated result // If output_shape doesn's have one, we set it to the scaled result if (dim->has_dim_value()) { if (static_cast(dim->dim_value()) != dim_value) { fail_shape_inference( "Dimension value inferred (", dim_value, ") is not equal to the existing dim value (", dim->dim_value(), ")."); } } else { dim->set_dim_value(static_cast(dim_value)); } // dim->has_dim_value() } // input_shape.dim(i).has_dim_value() } } void resizeShapeInference_opset7_to_10(InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } const auto& input_shape = getInputShape(ctx, 0); auto* output_shape = getOutputShape(ctx, 0); const auto scales = ctx.getInputData(1); if (output_shape->dim_size() > 0) { if (output_shape->dim_size() != input_shape.dim_size()) { fail_shape_inference( "Ranks inferred (", input_shape.dim_size(), ") is not equal to the existing rank value (", output_shape->dim_size(), ")."); } } else { // Infer the rank of output anyway for (int i = 0; i < input_shape.dim_size(); ++i) { output_shape->add_dim(); } } if (nullptr != scales) { // Infer output shape's dimension value if 'scales' is known. if (scales->data_type() == TensorProto::FLOAT) { const auto& scales_data = ParseData(scales); if (scales_data.size() != static_cast(input_shape.dim_size())) { fail_shape_inference( "Number of elements of input 'scales' must be same as rank of input 'X'"); } resizeShapeInferenceHelper_opset7_to_10(input_shape, scales_data, output_shape); } else { fail_shape_inference("Input 'scales' must have float element type."); } // nullptr != scales } } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/operator_sets_ml.h0000664000000000000000000000730013655345213016645 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #ifdef ONNX_ML #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { // Forward declarations for ai.onnx.ml version 1 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, ArrayFeatureExtractor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, Binarizer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, CastMap); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, CategoryMapper); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, DictVectorizer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, FeatureVectorizer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, Imputer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, LabelEncoder); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, LinearClassifier); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, LinearRegressor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, Normalizer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, OneHotEncoder); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, SVMClassifier); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, SVMRegressor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, Scaler); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, TreeEnsembleClassifier); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, TreeEnsembleRegressor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 1, ZipMap); // Iterate over schema from ai.onnx.ml version 1 class OpSet_OnnxML_ver1 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxML, 2, LabelEncoder); class OpSet_OnnxML_ver2 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); } }; inline void RegisterOnnxMLOperatorSetSchema() { RegisterOpSetSchema(); RegisterOpSetSchema(); } } // namespace ONNX_NAMESPACE #endif onnx-1.7.0/onnx/defs/schema.cc0000664000000000000000000007770513655345213014702 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" #include #include #include "onnx/checker.h" #include "onnx/defs/operator_sets.h" #include "onnx/defs/operator_sets_training.h" #include "onnx/defs/operator_sets_preview.h" #ifdef ONNX_ML #include "onnx/defs/operator_sets_ml.h" #endif #include "onnx/common/assertions.h" #include "onnx/common/stl_backports.h" namespace ONNX_NAMESPACE { void RegisterSchema(OpSchema&& schema) { OpSchemaRegistry::OpSchemaRegisterOnce ONNX_UNUSED registration = schema; } #ifndef NDEBUG DbgOperatorSetTracker& DbgOperatorSetTracker::Instance() { static DbgOperatorSetTracker instance; return instance; } #endif const std::string& OpSchema::FormalParameter::GetName() const { return name_; } const DataTypeSet& OpSchema::FormalParameter::GetTypes() const { return type_set_; } DataTypeSet& OpSchema::FormalParameter::MutableTypes() { return type_set_; } const std::string& OpSchema::FormalParameter::GetTypeStr() const { return type_str_; } const std::string& OpSchema::FormalParameter::GetDescription() const { return description_; } OpSchema::FormalParameterOption OpSchema::FormalParameter::GetOption() const { return param_option_; } bool OpSchema::FormalParameter::GetIsHomogeneous() const { return is_homogeneous_; } int OpSchema::FormalParameter::GetMinArity() const { return min_arity_; } OpSchemaRegistry* OpSchemaRegistry::Instance() { static OpSchemaRegistry instance; return &instance; } void OpSchema::CheckInputOutputType(struct InferenceContext& ctx) const { std::unordered_map type_constraints; // check all input types for (size_t in_idx = 0; in_idx < ctx.getNumInputs() && in_idx < inputs_.size(); ++in_idx) { const auto& param = inputs_[in_idx]; const auto& type_str = param.GetTypeStr(); const auto& param_type = ctx.getInputType(in_idx); const auto& all_types = param.GetTypes(); if (nullptr == param_type || param_type->value_case() == TypeProto::VALUE_NOT_SET) { continue; } else if ( !all_types.empty() && all_types.find(Utils::DataTypeUtils::ToType(*param_type)) == all_types.end()) { fail_check( param.GetName(), " typestr: ", type_str, ", has unsupported type: ", *Utils::DataTypeUtils::ToType(*param_type)); } if (param.GetIsHomogeneous()) { const auto& type_proto = Utils::DataTypeUtils::ToType(*param_type); if (type_constraints.find(type_str) == type_constraints.end()) { type_constraints[type_str] = *type_proto; } else if (type_constraints[type_str] != *type_proto) { fail_check( param.GetName(), " has inconsistent type ", *Utils::DataTypeUtils::ToType(*param_type)); } } } // for inputs // check all output types for (size_t out_idx = 0; out_idx < ctx.getNumOutputs() && out_idx < outputs_.size(); ++out_idx) { const auto& param = outputs_[out_idx]; const auto& type_str = param.GetTypeStr(); const auto& param_type = ctx.getOutputType(out_idx); const auto& all_types = param.GetTypes(); bool output_type_found = true; // infer type if necessary if (param_type->value_case() == TypeProto::VALUE_NOT_SET) { if (all_types.size() == 1) { *param_type = Utils::DataTypeUtils::ToTypeProto(*all_types.begin()); } else if (type_constraints.find(type_str) != type_constraints.end()) { auto data_type = Utils::DataTypeUtils::ToType(type_constraints[type_str]); *param_type = Utils::DataTypeUtils::ToTypeProto(data_type); } else { output_type_found = false; } } if (!output_type_found) { continue; } if (!all_types.empty() && all_types.find(Utils::DataTypeUtils::ToType(*param_type)) == all_types.end()) { fail_check( param.GetName(), " has unsupported type ", *Utils::DataTypeUtils::ToType(*param_type)); } if (param.GetIsHomogeneous()) { const auto& type_proto = Utils::DataTypeUtils::ToType(*param_type); if (type_constraints.find(type_str) == type_constraints.end()) { type_constraints[type_str] = *type_proto; } else if (type_constraints[type_str] != *type_proto) { fail_check( param.GetName(), " has inconsistent type ", *Utils::DataTypeUtils::ToType(*param_type)); } } // else } // for outputs } void OpSchema::Verify(const NodeProto& node) const { if (deprecated_) { fail_check( "Operator '", name_, "' has been deprecated since version ", since_version_); } // Check the number of inputs. if (node.input_size() < min_input_ || node.input_size() > max_input_) { fail_check( "Node (", node.name(), ") has input size ", node.input_size(), " not in range [min=", min_input_, ", max=", max_input_, "]."); } if (!num_inputs_allowed_(node.input_size())) { fail_check( "Node (", node.name(), ") has input size ", node.input_size(), " not in allowed input sizes."); } // Check the number of outputs. if (node.output_size() < min_output_ || node.output_size() > max_output_) { fail_check( "Node (", node.name(), ") has output size ", node.output_size(), " not in range [min=", min_output_, ", max=", max_output_, "]."); } if (!num_outputs_allowed_(node.output_size())) { fail_check( "Node (", node.name(), "has output size ", node.output_size(), " not in allowed output sizes."); } // Check the values of inputs / outputs for (int in_idx = 0; in_idx < node.input_size(); ++in_idx) { if (in_idx >= static_cast(inputs_.size())) { if (inputs_.size() > 0 && Variadic == inputs_.back().GetOption()) { // The last input formal parameter should be variadic. break; } else { fail_check( "Node (", node.name(), ") has more inputs (", node.input_size(), ") than declared (", inputs_.size(), ") in op definition."); } } if (node.input(in_idx).empty() && (Single == inputs_[in_idx].GetOption())) { fail_check( "Node (", node.name(), ")'s input ", in_idx, " is marked single but has an empty string in the graph"); } } for (int out_idx = 0; out_idx < node.output_size(); ++out_idx) { if (out_idx >= static_cast(outputs_.size())) { if (outputs_.size() > 0 && Variadic == outputs_.back().GetOption()) { // The last output formal parameter should be variadic. break; } else { fail_check( "Node (", node.name(), ") has more outputs (", node.output_size(), ") than declared (", outputs_.size(), ") in op definition."); } } if (node.output(out_idx).empty() && (Single == outputs_[out_idx].GetOption())) { fail_check( "Node (", node.name(), ")'s output ", out_idx, " is marked single but has an empty string in the graph"); } } // An internal symbol is defined as starting with two underscores. Attributes // with names meeting this condition are considered implementation details // and should be ignored for the purpose of schema checking. auto isInternalSymbol = [](const std::string& sym) -> bool { return sym.length() >= 2 && sym[0] == '_' && sym[1] == '_'; }; // Check attributes std::unordered_set seen_attr_names{}; for (const auto& attr_proto : node.attribute()) { const auto& name = attr_proto.name(); if (!seen_attr_names.insert(name).second) { fail_check("Attribute '", name, "' appeared multiple times."); }; const auto& search = attributes_.find(name); AttributeProto::AttributeType expected_type; if (search != attributes_.end()) { expected_type = search->second.type; } else if (allows_unchecked_attributes_ || isInternalSymbol(name)) { continue; } else { fail_check( "Unrecognized attribute: ", name, " for operator ", node.op_type()); } // Type would be UNDEFINED if not set if (attr_proto.type() != expected_type) { fail_check( "Mismatched attribute type in '", node.name() + " : " + name, "'"); } // ref_attr_name is only valid when non-empty // we simply read default value if not present if (!attr_proto.ref_attr_name().empty()) { continue; } switch (expected_type) { // if attr_proto().type() != UNDEFINED // we consider primitive types to be set even // if proto3 did not output default values into the stream // in which case we will read the default case AttributeProto::FLOAT: case AttributeProto::INT: case AttributeProto::STRING: break; case AttributeProto::TENSOR: if (!attr_proto.has_t()) { fail_check("Attribute '", name, "' is expected to have field 't'"); } break; case AttributeProto::SPARSE_TENSOR: if (!attr_proto.has_sparse_tensor()) { fail_check( "Attribute '", name, "' is expected to have field 'sparse_tensor'"); } break; case AttributeProto::GRAPH: if (!attr_proto.has_g()) { fail_check("Attribute '", name, "' is expected to have field 'g'"); } break; case AttributeProto::FLOATS: if (!attr_proto.floats_size()) { fail_check( "Attribute '", name, "' is expected to have field 'floats'"); } break; case AttributeProto::INTS: if (!attr_proto.ints_size()) { fail_check("Attribute '", name, "' is expected to have field 'ints'"); } break; case AttributeProto::STRINGS: if (!attr_proto.strings_size()) { fail_check( "Attribute '", name, "' is expected to have field 'strings'"); } break; case AttributeProto::TENSORS: if (!attr_proto.tensors_size()) { fail_check( "Attribute '", name, "' is expected to have field 'tensors'"); } break; case AttributeProto::SPARSE_TENSORS: // Not adding check ... we should likely delete the check in all other // cases, which will not allow us to have an empty list as a valid value // for an attribute and this seems undesirable. break; case AttributeProto::GRAPHS: if (!attr_proto.graphs_size()) { fail_check( "Attribute '", name, "' is expected to have field 'graphs'"); } break; default: fail_check("Attribute '", name, " has unknown expected type"); } } for (const auto& pair : attributes_) { const auto& attr = pair.second; if (!attr.required) { continue; } if (!seen_attr_names.count(attr.name)) { fail_check("Required attribute '", attr.name, "' is missing."); } } // Phew. All verifications passed. } OpSchema& OpSchema::SinceVersion(OperatorSetVersion v) { since_version_ = v; return *this; } OpSchema& OpSchema::Deprecate() { deprecated_ = true; return *this; } OpSchema& OpSchema::NumInputs(std::set allowed_input_nums) { num_inputs_allowed_ = [MOVE_CAPTURE_IF_CPP14(allowed_input_nums)](int n) -> bool { return allowed_input_nums.count(n); }; return *this; } OpSchema& OpSchema::NumOutputs(std::set allowed_output_nums) { num_outputs_allowed_ = [MOVE_CAPTURE_IF_CPP14(allowed_output_nums)](int n) -> bool { return allowed_output_nums.count(n); }; return *this; } OpSchema& OpSchema::TypeAndShapeInferenceFunction( InferenceFunction inferenceFunction) { tensor_inference_function_ = inferenceFunction; return *this; } OpSchema& OpSchema::SetSupportLevel(SupportType support) { support_ = support; return *this; } // Functions to specify name for the operator schema. OpSchema& OpSchema::SetName(std::string name) { name_ = std::move(name); return *this; } OpSchema& OpSchema::SetName(const char* name) { return SetName(std::string(name)); } // Functions to specify code location for the operator schema. OpSchema& OpSchema::SetLocation(std::string file, int line) { file_ = std::move(file); line_ = line; return *this; } OpSchema& OpSchema::SetLocation(const char* file, int line) { return SetLocation(std::string(file), line); } OpSchema& OpSchema::SetDomain(std::string domain) { domain_ = std::move(domain); return *this; } OpSchema& OpSchema::SetDomain(const char* domain) { return SetDomain(std::string(domain)); } OpSchema& OpSchema::Attr(Attribute attr) { auto name = attr.name; // copy name so we can move attr in the next line attributes_.insert(std::make_pair(std::move(name), std::move(attr))); return *this; } OpSchema& OpSchema::Attr( std::string name, std::string description, AttributeProto::AttributeType type, bool required) { Attr(Attribute{std::move(name), std::move(description), type, required}); return *this; } OpSchema& OpSchema::Attr( const char* name, const char* description, AttributeProto::AttributeType type, bool required) { return Attr(std::string(name), std::string(description), type, required); } #define ATTR_SETTER_WITH_SINGLE_VALUE(type, field, attrtype) \ OpSchema& OpSchema::Attr( \ std::string name, \ std::string description, \ AttributeProto::AttributeType attr_type, \ const type& default_value) { \ if (attrtype != attr_type) { \ fail_schema("Attribute specification type mismatch."); \ } \ AttributeProto a; \ a.set_name(name); \ a.set_##field(default_value); \ a.set_type(attr_type); \ Attr(Attribute(std::move(name), std::move(description), std::move(a))); \ return *this; \ } \ OpSchema& OpSchema::Attr( \ const char* name, \ const char* description, \ AttributeProto::AttributeType attr_type, \ const type& default_value) { \ return Attr( \ std::string(name), \ std::string(description), \ attr_type, \ default_value); \ } #define ATTR_SETTER_WITH_LIST_VALUE(type, field, attrtype) \ OpSchema& OpSchema::Attr( \ std::string name, \ std::string description, \ AttributeProto::AttributeType attr_type, \ const std::vector& default_value) { \ if (attrtype != attr_type) { \ fail_schema("Attribute specification type mismatch."); \ } \ AttributeProto a; \ a.set_name(name); \ a.set_type(attr_type); \ for (const auto& v : default_value) { \ a.add_##field(v); \ } \ Attr(Attribute(std::move(name), std::move(description), std::move(a))); \ return *this; \ } #define ATTR_SETTER_WITH_SINGLE_COMPLEXVALUE(type, field, attrtype) \ OpSchema& OpSchema::Attr( \ std::string name, \ std::string description, \ AttributeProto::AttributeType attr_type, \ const type& default_value) { \ if (attrtype != attr_type) { \ fail_schema("Attribute specification type mismatch."); \ } \ AttributeProto a; \ a.set_name(name); \ *(a.mutable_##field()) = default_value; \ a.set_type(attr_type); \ Attr(Attribute(std::move(name), std::move(description), a)); \ return *this; \ } #define ATTR_SETTER_WITH_LIST_COMPLEXVALUE(type, field, attrtype) \ OpSchema& OpSchema::Attr( \ std::string name, \ std::string description, \ AttributeProto::AttributeType attr_type, \ const std::vector& default_value) { \ if (attrtype != attr_type) { \ fail_schema("Attribute specification type mismatch."); \ } \ AttributeProto a; \ a.set_name(name); \ a.set_type(attr_type); \ for (const auto& v : default_value) { \ *(a.add_##field()) = v; \ } \ Attr(Attribute(std::move(name), std::move(description), std::move(a))); \ return *this; \ } ATTR_SETTER_WITH_SINGLE_VALUE(int64_t, i, AttributeProto::INT) ATTR_SETTER_WITH_SINGLE_VALUE(float, f, AttributeProto::FLOAT) ATTR_SETTER_WITH_SINGLE_VALUE(std::string, s, AttributeProto::STRING) ATTR_SETTER_WITH_SINGLE_COMPLEXVALUE(TensorProto, t, AttributeProto::TENSOR) ATTR_SETTER_WITH_SINGLE_COMPLEXVALUE(GraphProto, g, AttributeProto::GRAPH) ATTR_SETTER_WITH_LIST_VALUE(int64_t, ints, AttributeProto::INTS) ATTR_SETTER_WITH_LIST_VALUE(float, floats, AttributeProto::FLOATS) ATTR_SETTER_WITH_LIST_COMPLEXVALUE( std::string, strings, AttributeProto::STRINGS) ATTR_SETTER_WITH_LIST_COMPLEXVALUE( TensorProto, tensors, AttributeProto::TENSORS) ATTR_SETTER_WITH_LIST_COMPLEXVALUE(GraphProto, graphs, AttributeProto::GRAPHS) OpSchema& OpSchema::AllowUncheckedAttributes() { allows_unchecked_attributes_ = true; return *this; } OpSchema& OpSchema::Input( int n, std::string name, const std::string& description, std::string type_str, OpSchema::FormalParameterOption param_option, bool is_homogeneous, int min_arity) { if (int(inputs_.size()) <= n) { inputs_.resize(n + 1); } inputs_[n] = FormalParameter( std::move(name), #ifndef __ONNX_NO_DOC_STRINGS description, #else std::string(), #endif std::move(type_str), param_option, is_homogeneous, min_arity); return *this; } OpSchema& OpSchema::Input( int n, const char* name, const char* description, const char* type_str, FormalParameterOption param_option, bool is_homogeneous, int min_arity) { return Input( n, std::string(name), #ifndef __ONNX_NO_DOC_STRINGS std::string(description), #else std::string(), #endif std::string(type_str), param_option, is_homogeneous, min_arity); } OpSchema& OpSchema::Output( int n, std::string name, const std::string& description, std::string type_str, OpSchema::FormalParameterOption param_option, bool is_homogeneous, int min_arity) { if (int(outputs_.size()) <= n) { outputs_.resize(n + 1); } outputs_[n] = FormalParameter( std::move(name), #ifndef __ONNX_NO_DOC_STRINGS description, #else std::string(), #endif std::move(type_str), param_option, is_homogeneous, min_arity); return *this; } OpSchema& OpSchema::Output( int n, const char* name, const char* description, const char* type_str, FormalParameterOption param_option, bool is_homogeneous, int min_arity) { return Output( n, std::string(name), #ifndef __ONNX_NO_DOC_STRINGS std::string(description), #else std::string(), #endif std::string(type_str), param_option, is_homogeneous, min_arity); } OpSchema& OpSchema::TypeConstraint( std::string type_str, std::vector constraints, std::string description) { if (type_constraints_.end() != type_constraints_.find(type_str)) { fail_schema("Duplicate type constraint name"); } DataTypeSet d; for (const auto& t : constraints) { d.insert(Utils::DataTypeUtils::ToType(t)); } type_constraints_.insert( std::make_pair(type_str, std::make_pair(d, description))); type_constraint_params_.push_back(TypeConstraintParam( std::move(type_str), std::move(constraints), std::move(description))); return *this; } OpSchema& OpSchema::TypeConstraint( const char* type_str, std::initializer_list constraints, const char* description) { std::vector constraints_vector; constraints_vector.reserve(constraints.size()); for (auto iter = constraints.begin(); iter != constraints.end(); ++iter) { constraints_vector.push_back(*iter); } return TypeConstraint( std::string(type_str), constraints_vector, std::string(description)); } void OpSchema::ParseAndSetTypes( /*out*/ std::vector* formal_parameters) { for (auto& formal_parameter : *formal_parameters) { auto& type = formal_parameter.GetTypeStr(); DataTypeSet allowed_types; auto it = type_constraints_.find(type); if (it != type_constraints_.end()) { allowed_types = it->second.first; } else { allowed_types.emplace(Utils::DataTypeUtils::ToType(type)); } formal_parameter.MutableTypes() = allowed_types; } } OpSchema& OpSchema::SetContextDependentFunctionBodyBuilder( ContextDependentFunctionBodyBuilder functionBuilder) { functionBuilder_ = functionBuilder; return *this; } bool OpSchema::BuildContextDependentFunction( const FunctionBodyBuildContext& ctx, FunctionProto& functionProto) const { if (functionBuilder_) return functionBuilder_(ctx, *this, functionProto); else return false; } OpSchema& OpSchema::FunctionBody(const std::vector& func_nodes) { for (const auto& node : func_nodes) { auto new_node = function_body_.add_node(); new_node->CopyFrom(node); } return *this; } OpSchema& OpSchema::FunctionBody( const std::vector& func_nodes, const std::vector& relied_opsets) { for (auto& relied_opset : relied_opsets) { *(function_body_.mutable_opset_import()->Add()) = relied_opset; } return FunctionBody(func_nodes); } const FunctionProto* OpSchema::GetFunction() const { return function_body_.node_size() > 0 ? &function_body_ : nullptr; } OpSchema& OpSchema::FillUsing(const std::function& populator) { if (populator) { populator(*this); } return *this; } void OpSchema::BuildFunction(FunctionProto& function_body) const { function_body.set_name(this->name_); function_body.set_doc_string(this->doc_); function_body.set_since_version(this->since_version_); function_body.set_status(OperatorStatus(1 - (int)this->support_)); for (auto& i : inputs_) { function_body.add_input(i.GetName()); } for (auto& o : outputs_) { function_body.add_output(o.GetName()); } for (auto& a : attributes_) { function_body.add_attribute(a.first); } // By default, the function body graph is relying on the OperatorSet this // function belongs to. auto relied_opset = function_body.mutable_opset_import()->Add(); relied_opset->set_domain(this->domain()); relied_opset->set_version(this->SinceVersion()); } void OpSchema::Finalize() { #define ENFORCE(x) \ do { \ if (!(x)) \ throw std::logic_error( \ "ONNX Schema " + name_ + ": failed validating the check: " + #x); \ } while (0) // Calculate min/max number of inputs. // = + . = ::max() (if the last input is // variadic). // Flag indicates whether an optional input is trailing one (there's no single // or variadic input behind). for (size_t i = 0; i < inputs_.size(); ++i) { switch (inputs_[i].GetOption()) { case OpSchema::Single: ++max_input_; min_input_ = max_input_; break; case OpSchema::Optional: ++max_input_; break; case OpSchema::Variadic: // Only last input formal parameter could be variadic. ENFORCE((inputs_.size() - 1) == i); min_input_ = max_input_ + inputs_[i].GetMinArity(); max_input_ = std::numeric_limits::max(); break; } } // Calculate min/max number of outputs. for (size_t i = 0; i < outputs_.size(); ++i) { switch (outputs_[i].GetOption()) { case OpSchema::Single: ++max_output_; min_output_ = max_output_; break; case OpSchema::Optional: ++max_output_; break; case OpSchema::Variadic: // Only last output formal parameter could be variadic. ENFORCE((outputs_.size() - 1) == i); min_output_ = max_output_ + outputs_[i].GetMinArity(); max_output_ = std::numeric_limits::max(); break; } } // all inputs and outputs have names for (const auto& it : inputs_) { ENFORCE(!(it.GetName().empty())); } for (const auto& it : outputs_) { ENFORCE(!(it.GetName().empty())); } ParseAndSetTypes(&inputs_); ParseAndSetTypes(&outputs_); if (this->HasFunction()) { BuildFunction(function_body_); } } std::ostream& operator<<(std::ostream& out, const OpSchema& schema) { if (!schema.attributes_.empty()) { out << "Attributes:" << std::endl; for (const auto& pair : schema.attributes_) { out << " " << pair.second.name << " : " << pair.second.description << std::endl; } } if (schema.max_input_ > 0) { out << "Inputs:" << std::endl; if (!schema.inputs_.empty()) { for (size_t i = 0; i < schema.inputs_.size(); ++i) { const auto& p = schema.inputs_[i]; const auto& name = p.GetName(); const auto& description = p.GetDescription(); const auto& type_str = p.GetTypeStr(); out << " " << i << ", " << ("" != name ? name : "(unnamed)") << " : " << ("" != description ? description : "(no doc)") << " : " << ("" != type_str ? type_str : "(no type)") << std::endl; } } else { out << " (no explicit description available)" << std::endl; } } if (schema.max_output_ > 0) { out << "Outputs:" << std::endl; if (!schema.outputs_.empty()) { for (size_t i = 0; i < schema.outputs_.size(); ++i) { const auto& p = schema.outputs_[i]; const auto& name = p.GetName(); const auto& description = p.GetDescription(); const auto& type_str = p.GetTypeStr(); out << " " << i << ", " << ("" != name ? name : "(unnamed)") << " : " << ("" != description ? description : "(no doc)") << " : " << ("" != type_str ? type_str : "(no type)") << std::endl; } } else { out << " (no explicit description available)" << std::endl; } } out << std::endl; if (schema.doc()) { out << schema.doc(); } else { out << "(no documentation yet)" << std::endl; } out << std::endl; if (schema.line_) { out << "Defined at " << schema.file_ << ":" << schema.line_ << std::endl; } return out; } OpSchemaRegistry::DomainToVersionRange& OpSchemaRegistry::DomainToVersionRange::Instance() { static DomainToVersionRange domain_to_version_range; return domain_to_version_range; }; // Private method used by OpSchemaRegisterOnce and OpSchemaRegistry::map() OpName_Domain_Version_Schema_Map& OpSchemaRegistry::GetMapWithoutEnsuringRegistration() { static OpName_Domain_Version_Schema_Map map; return map; } OpName_Domain_Version_Schema_Map& OpSchemaRegistry::map() { auto& map = GetMapWithoutEnsuringRegistration(); // The following class is used to register operators the // first time this method is called, in a thread-safe fashion. class SchemasRegisterer { public: SchemasRegisterer() { // In debug builds, the number of schema registered in this constructor // is compared against the number of calls to schema registration macros. #ifndef NDEBUG size_t dbg_initial_schema_count = GetRegisteredSchemaCount(); #endif RegisterOnnxOperatorSetSchema(); #ifdef ONNX_ML RegisterOnnxMLOperatorSetSchema(); #endif // Invoke register of training operators. RegisterOnnxTrainingOperatorSetSchema(); // Invoke register of experimental operators. RegisterOnnxPreviewOperatorSetSchema(); #ifndef NDEBUG size_t dbg_registered_schema_count = GetRegisteredSchemaCount() - dbg_initial_schema_count; ONNX_ASSERTM( dbg_registered_schema_count == ONNX_DBG_GET_COUNT_IN_OPSETS(), "%u schema were exposed from operator sets and automatically placed into the static registry. " "%u were expected based on calls to registration macros. Operator set functions may need to be updated.", dbg_registered_schema_count, ONNX_DBG_GET_COUNT_IN_OPSETS()); #endif } private: static size_t GetRegisteredSchemaCount() { size_t count = 0; for (auto& x : GetMapWithoutEnsuringRegistration()) { for (auto& y : x.second) { count += y.second.size(); } } return count; } }; #ifndef __ONNX_DISABLE_STATIC_REGISTRATION static SchemasRegisterer schemasRegisterer; #endif return map; } size_t ReplaceAll(std::string& s, const char* from, const char* to) { size_t numReplaced = 0; std::string::size_type lenFrom = std::strlen(from); std::string::size_type lenTo = std::strlen(to); for (std::string::size_type pos = s.find(from); pos != std::string::npos; pos = s.find(from, pos + lenTo)) { s.replace(pos, lenFrom, to); numReplaced++; } return numReplaced; } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/gen_shape_inference_information.py0000664000000000000000000000153413655345213022044 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from onnx import defs def main(): # type: () -> None # domain -> support level -> name -> [schema] with_inference = [] without_inference = [] for schema in defs.get_all_schemas(): domain, name, has_inference = schema.domain, schema.name, schema.has_type_and_shape_inference_function if has_inference: with_inference.append((domain, name)) else: without_inference.append((domain, name)) print(len(with_inference), 'operators have a type/shape inference function.') print(len(without_inference), 'do not. These are:') for domain, name in sorted(without_inference): print(domain, name) if __name__ == '__main__': main() onnx-1.7.0/onnx/defs/data_type_utils.cc0000664000000000000000000003132013655345213016613 0ustar rootroot#include #include #include #include #include "data_type_utils.h" namespace ONNX_NAMESPACE { namespace Utils { // Singleton wrapper around allowed data types. // This implements construct on first use which is needed to ensure // static objects are initialized before use. Ops registration does not work // properly without this. class TypesWrapper final { public: static TypesWrapper& GetTypesWrapper(); std::unordered_set& GetAllowedDataTypes(); std::unordered_map& TypeStrToTensorDataType(); std::unordered_map& TensorDataTypeToTypeStr(); ~TypesWrapper() = default; TypesWrapper(const TypesWrapper&) = delete; void operator=(const TypesWrapper&) = delete; private: TypesWrapper(); std::unordered_map type_str_to_tensor_data_type_; std::unordered_map tensor_data_type_to_type_str_; std::unordered_set allowed_data_types_; }; // Simple class which contains pointers to external string buffer and a size. // This can be used to track a "valid" range/slice of the string. // Caller should ensure StringRange is not used after external storage has // been freed. class StringRange final { public: StringRange(); StringRange(const char* data, size_t size); StringRange(const std::string& str); StringRange(const char* data); const char* Data() const; size_t Size() const; bool Empty() const; char operator[](size_t idx) const; void Reset(); void Reset(const char* data, size_t size); void Reset(const std::string& str); bool StartsWith(const StringRange& str) const; bool EndsWith(const StringRange& str) const; bool LStrip(); bool LStrip(size_t size); bool LStrip(StringRange str); bool RStrip(); bool RStrip(size_t size); bool RStrip(StringRange str); bool LAndRStrip(); void ParensWhitespaceStrip(); size_t Find(const char ch) const; // These methods provide a way to return the range of the string // which was discarded by LStrip(). i.e. We capture the string // range which was discarded. StringRange GetCaptured(); void RestartCapture(); private: // data_ + size tracks the "valid" range of the external string buffer. const char* data_; size_t size_; // start_ and end_ track the captured range. // end_ advances when LStrip() is called. const char* start_; const char* end_; }; std::unordered_map& DataTypeUtils::GetTypeStrToProtoMap() { static std::unordered_map map; return map; } std::mutex& DataTypeUtils::GetTypeStrLock() { static std::mutex lock; return lock; } DataType DataTypeUtils::ToType(const TypeProto& type_proto) { auto typeStr = ToString(type_proto); std::lock_guard lock(GetTypeStrLock()); if (GetTypeStrToProtoMap().find(typeStr) == GetTypeStrToProtoMap().end()) { TypeProto type; FromString(typeStr, type); GetTypeStrToProtoMap()[typeStr] = type; } return &(GetTypeStrToProtoMap().find(typeStr)->first); } DataType DataTypeUtils::ToType(const std::string& type_str) { TypeProto type; FromString(type_str, type); return ToType(type); } const TypeProto& DataTypeUtils::ToTypeProto(const DataType& data_type) { std::lock_guard lock(GetTypeStrLock()); auto it = GetTypeStrToProtoMap().find(*data_type); assert(it != GetTypeStrToProtoMap().end()); return it->second; } std::string DataTypeUtils::ToString( const TypeProto& type_proto, const std::string& left, const std::string& right) { switch (type_proto.value_case()) { case TypeProto::ValueCase::kTensorType: { // Note: We do not distinguish tensors with zero rank (a shape consisting // of an empty sequence of dimensions) here. return left + "tensor(" + ToDataTypeString(type_proto.tensor_type().elem_type()) + ")" + right; } case TypeProto::ValueCase::kSequenceType: { return ToString( type_proto.sequence_type().elem_type(), left + "seq(", ")" + right); } case TypeProto::ValueCase::kMapType: { std::string map_str = "map(" + ToDataTypeString(type_proto.map_type().key_type()) + ","; return ToString( type_proto.map_type().value_type(), left + map_str, ")" + right); } #ifdef ONNX_ML case TypeProto::ValueCase::kOpaqueType: { static const std::string empty; std::string result; const auto& op_type = type_proto.opaque_type(); result.append(left).append("opaque("); if (op_type.has_domain() && !op_type.domain().empty()) { result.append(op_type.domain()).append(","); } if (op_type.has_name() && !op_type.name().empty()) { result.append(op_type.name()); } result.append(")").append(right); return result; } case TypeProto::ValueCase::kSparseTensorType: { // Note: We do not distinguish tensors with zero rank (a shape consisting // of an empty sequence of dimensions) here. return left + "sparse_tensor(" + ToDataTypeString(type_proto.sparse_tensor_type().elem_type()) + ")" + right; } #endif default: assert(false); return std::string(); } } std::string DataTypeUtils::ToDataTypeString(int32_t tensor_data_type) { TypesWrapper& t = TypesWrapper::GetTypesWrapper(); auto iter = t.TensorDataTypeToTypeStr().find(tensor_data_type); assert(t.TensorDataTypeToTypeStr().end() != iter); return iter->second; } void DataTypeUtils::FromString( const std::string& type_str, TypeProto& type_proto) { StringRange s(type_str); type_proto.Clear(); if (s.LStrip("seq")) { s.ParensWhitespaceStrip(); return FromString( std::string(s.Data(), s.Size()), *type_proto.mutable_sequence_type()->mutable_elem_type()); } else if (s.LStrip("map")) { s.ParensWhitespaceStrip(); size_t key_size = s.Find(','); StringRange k(s.Data(), key_size); std::string key(k.Data(), k.Size()); s.LStrip(key_size); s.LStrip(","); StringRange v(s.Data(), s.Size()); int32_t key_type; FromDataTypeString(key, key_type); type_proto.mutable_map_type()->set_key_type(key_type); return FromString( std::string(v.Data(), v.Size()), *type_proto.mutable_map_type()->mutable_value_type()); } else #ifdef ONNX_ML if (s.LStrip("opaque")) { auto* opaque_type = type_proto.mutable_opaque_type(); s.ParensWhitespaceStrip(); if (!s.Empty()) { size_t cm = s.Find(','); if (cm != std::string::npos) { if (cm > 0) { opaque_type->mutable_domain()->assign(s.Data(), cm); } s.LStrip(cm + 1); // skip comma } if (!s.Empty()) { opaque_type->mutable_name()->assign(s.Data(), s.Size()); } } } else if (s.LStrip("sparse_tensor")) { s.ParensWhitespaceStrip(); int32_t e; FromDataTypeString(std::string(s.Data(), s.Size()), e); type_proto.mutable_sparse_tensor_type()->set_elem_type(e); } else #endif if (s.LStrip("tensor")) { s.ParensWhitespaceStrip(); int32_t e; FromDataTypeString(std::string(s.Data(), s.Size()), e); type_proto.mutable_tensor_type()->set_elem_type(e); } else { // Scalar int32_t e; FromDataTypeString(std::string(s.Data(), s.Size()), e); TypeProto::Tensor* t = type_proto.mutable_tensor_type(); t->set_elem_type(e); // Call mutable_shape() to initialize a shape with no dimension. t->mutable_shape(); } } // namespace Utils bool DataTypeUtils::IsValidDataTypeString(const std::string& type_str) { TypesWrapper& t = TypesWrapper::GetTypesWrapper(); const auto& allowedSet = t.GetAllowedDataTypes(); return (allowedSet.find(type_str) != allowedSet.end()); } void DataTypeUtils::FromDataTypeString( const std::string& type_str, int32_t& tensor_data_type) { assert(IsValidDataTypeString(type_str)); TypesWrapper& t = TypesWrapper::GetTypesWrapper(); tensor_data_type = t.TypeStrToTensorDataType()[type_str]; } StringRange::StringRange() : data_(""), size_(0), start_(data_), end_(data_) {} StringRange::StringRange(const char* p_data, size_t p_size) : data_(p_data), size_(p_size), start_(data_), end_(data_) { assert(p_data != nullptr); LAndRStrip(); } StringRange::StringRange(const std::string& p_str) : data_(p_str.data()), size_(p_str.size()), start_(data_), end_(data_) { LAndRStrip(); } StringRange::StringRange(const char* p_data) : data_(p_data), size_(strlen(p_data)), start_(data_), end_(data_) { LAndRStrip(); } const char* StringRange::Data() const { return data_; } size_t StringRange::Size() const { return size_; } bool StringRange::Empty() const { return size_ == 0; } char StringRange::operator[](size_t idx) const { return data_[idx]; } void StringRange::Reset() { data_ = ""; size_ = 0; start_ = end_ = data_; } void StringRange::Reset(const char* data, size_t size) { data_ = data; size_ = size; start_ = end_ = data_; } void StringRange::Reset(const std::string& str) { data_ = str.data(); size_ = str.size(); start_ = end_ = data_; } bool StringRange::StartsWith(const StringRange& str) const { return ((size_ >= str.size_) && (memcmp(data_, str.data_, str.size_) == 0)); } bool StringRange::EndsWith(const StringRange& str) const { return ( (size_ >= str.size_) && (memcmp(data_ + (size_ - str.size_), str.data_, str.size_) == 0)); } bool StringRange::LStrip() { size_t count = 0; const char* ptr = data_; while (count < size_ && isspace(*ptr)) { count++; ptr++; } if (count > 0) { return LStrip(count); } return false; } bool StringRange::LStrip(size_t size) { if (size <= size_) { data_ += size; size_ -= size; end_ += size; return true; } return false; } bool StringRange::LStrip(StringRange str) { if (StartsWith(str)) { return LStrip(str.size_); } return false; } bool StringRange::RStrip() { size_t count = 0; const char* ptr = data_ + size_ - 1; while (count < size_ && isspace(*ptr)) { ++count; --ptr; } if (count > 0) { return RStrip(count); } return false; } bool StringRange::RStrip(size_t size) { if (size_ >= size) { size_ -= size; return true; } return false; } bool StringRange::RStrip(StringRange str) { if (EndsWith(str)) { return RStrip(str.size_); } return false; } bool StringRange::LAndRStrip() { bool l = LStrip(); bool r = RStrip(); return l || r; } void StringRange::ParensWhitespaceStrip() { LStrip(); LStrip("("); LAndRStrip(); RStrip(")"); RStrip(); } size_t StringRange::Find(const char ch) const { size_t idx = 0; while (idx < size_) { if (data_[idx] == ch) { return idx; } idx++; } return std::string::npos; } void StringRange::RestartCapture() { start_ = data_; end_ = data_; } StringRange StringRange::GetCaptured() { return StringRange(start_, end_ - start_); } TypesWrapper& TypesWrapper::GetTypesWrapper() { static TypesWrapper types; return types; } std::unordered_set& TypesWrapper::GetAllowedDataTypes() { return allowed_data_types_; } std::unordered_map& TypesWrapper::TypeStrToTensorDataType() { return type_str_to_tensor_data_type_; } std::unordered_map& TypesWrapper::TensorDataTypeToTypeStr() { return tensor_data_type_to_type_str_; } TypesWrapper::TypesWrapper() { // DataType strings. These should match the DataTypes defined in onnx.proto type_str_to_tensor_data_type_["float"] = TensorProto_DataType_FLOAT; type_str_to_tensor_data_type_["float16"] = TensorProto_DataType_FLOAT16; type_str_to_tensor_data_type_["bfloat16"] = TensorProto_DataType_BFLOAT16; type_str_to_tensor_data_type_["double"] = TensorProto_DataType_DOUBLE; type_str_to_tensor_data_type_["int8"] = TensorProto_DataType_INT8; type_str_to_tensor_data_type_["int16"] = TensorProto_DataType_INT16; type_str_to_tensor_data_type_["int32"] = TensorProto_DataType_INT32; type_str_to_tensor_data_type_["int64"] = TensorProto_DataType_INT64; type_str_to_tensor_data_type_["uint8"] = TensorProto_DataType_UINT8; type_str_to_tensor_data_type_["uint16"] = TensorProto_DataType_UINT16; type_str_to_tensor_data_type_["uint32"] = TensorProto_DataType_UINT32; type_str_to_tensor_data_type_["uint64"] = TensorProto_DataType_UINT64; type_str_to_tensor_data_type_["complex64"] = TensorProto_DataType_COMPLEX64; type_str_to_tensor_data_type_["complex128"] = TensorProto_DataType_COMPLEX128; type_str_to_tensor_data_type_["string"] = TensorProto_DataType_STRING; type_str_to_tensor_data_type_["bool"] = TensorProto_DataType_BOOL; for (auto& str_type_pair : type_str_to_tensor_data_type_) { tensor_data_type_to_type_str_[str_type_pair.second] = str_type_pair.first; allowed_data_types_.insert(str_type_pair.first); } } } // namespace Utils } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/traditionalml/0000775000000000000000000000000013655345213015756 5ustar rootrootonnx-1.7.0/onnx/defs/traditionalml/old.cc0000664000000000000000000000532113655345213017044 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" #ifdef ONNX_ML namespace ONNX_NAMESPACE { static const char* LabelEncoder_ver1_doc = R"DOC( Converts strings to integers and vice versa.
If the string default value is set, it will convert integers to strings. If the int default value is set, it will convert strings to integers.
Each operator converts either integers to strings or strings to integers, depending on which default value attribute is provided. Only one default value attribute should be defined.
When converting from integers to strings, the string is fetched from the 'classes_strings' list, by simple indexing.
When converting from strings to integers, the string is looked up in the list and the index at which it is found is used as the converted value. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( LabelEncoder, 1, OpSchema() .SetDoc(LabelEncoder_ver1_doc) .Input(0, "X", "Input data.", "T1") .Output(0, "Y", "Output data. If strings are input, the output values are integers, and vice versa.", "T2") .TypeConstraint( "T1", {"tensor(string)", "tensor(int64)"}, "The input type must be a tensor of integers or strings, of any shape.") .TypeConstraint( "T2", {"tensor(string)", "tensor(int64)"}, "The output type will be a tensor of strings or integers, and will have the same shape as the input.") .Attr( "classes_strings", "A list of labels.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "default_int64", "An integer to use when an input string value is not found in the map.
One and only one of the 'default_*' attributes must be defined.", AttributeProto::INT, static_cast(-1)) .Attr( "default_string", "A string to use when an input integer value is not found in the map.
One and only one of the 'default_*' attributes must be defined.", AttributeProto::STRING, std::string("_Unused")) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto input_elem_type = ctx.getInputType(0)->tensor_type().elem_type(); auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); if (TensorProto::STRING == input_elem_type) { output_elem_type->set_elem_type(TensorProto::INT64); } else if (TensorProto::INT64 == input_elem_type) { output_elem_type->set_elem_type(TensorProto::STRING); } })); } // namespace ONNX_NAMESPACE #endif onnx-1.7.0/onnx/defs/traditionalml/defs.cc0000664000000000000000000013727713655345213017227 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" #ifdef ONNX_ML namespace ONNX_NAMESPACE { static const char* ArrayFeatureExtractor_ver1_doc = R"DOC( Select elements of the input tensor based on the indices passed.
The indices are applied to the last axes of the tensor. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( ArrayFeatureExtractor, 1, OpSchema() .SetDoc(ArrayFeatureExtractor_ver1_doc) .Input(0, "X", "Data to be selected", "T") .Input( 1, "Y", "The indices, based on 0 as the first index of any dimension.", "tensor(int64)") .Output(0, "Z", "Selected output data as an array", "T") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)", "tensor(string)"}, "The input must be a tensor of a numeric type or string. The output will be of the same tensor type.")); static const char* Binarizer_ver1_doc = R"DOC( Maps the values of the input tensor to either 0 or 1, element-wise, based on the outcome of a comparison against a threshold value. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( Binarizer, 1, OpSchema() .SetDoc(Binarizer_ver1_doc) .Input(0, "X", "Data to be binarized", "T") .Output(0, "Y", "Binarized output data", "T") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input must be a tensor of a numeric type. The output will be of the same tensor type.") .Attr( "threshold", "Values greater than this are mapped to 1, others to 0.", AttributeProto::FLOAT, 0.f)); static const char* CastMap_ver1_doc = R"DOC( Converts a map to a tensor.
The map key must be an int64 and the values will be ordered in ascending order based on this key.
The operator supports dense packing or sparse packing. If using sparse packing, the key cannot exceed the max_map-1 value. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( CastMap, 1, OpSchema() .SetDoc(CastMap_ver1_doc) .Input(0, "X", "The input map that is to be cast to a tensor", "T1") .Output( 0, "Y", "A tensor representing the same data as the input map, ordered by their keys", "T2") .TypeConstraint( "T1", {"map(int64, string)", "map(int64, float)"}, "The input must be an integer map to either string or float.") .TypeConstraint( "T2", {"tensor(string)", "tensor(float)", "tensor(int64)"}, "The output is a 1-D tensor of string, float, or integer.") .Attr( "cast_to", "A string indicating the desired element type of the output tensor, one of 'TO_FLOAT', 'TO_STRING', 'TO_INT64'.", AttributeProto::STRING, std::string("TO_FLOAT")) .Attr( "map_form", "Indicates whether to only output as many values as are in the input (dense), or position the input based on using the key of the map as the index of the output (sparse).
One of 'DENSE', 'SPARSE'.", AttributeProto::STRING, std::string("DENSE")) .Attr( "max_map", "If the value of map_form is 'SPARSE,' this attribute indicates the total length of the output tensor.", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto cast_to_attr = ctx.getAttribute("cast_to"); auto output_type = ctx.getOutputType(0)->mutable_tensor_type(); if (nullptr == cast_to_attr) { output_type->set_elem_type(TensorProto::FLOAT); return; } auto& cast_to = cast_to_attr->s(); if (0 == cast_to.compare("TO_FLOAT")) { output_type->set_elem_type(TensorProto::FLOAT); } else if (0 == cast_to.compare("TO_INT64")) { output_type->set_elem_type(TensorProto::INT64); } else if (0 == cast_to.compare("TO_STRING")) { output_type->set_elem_type(TensorProto::STRING); } })); static const char* CategoryMapper_ver1_doc = R"DOC( Converts strings to integers and vice versa.
Two sequences of equal length are used to map between integers and strings, with strings and integers at the same index detailing the mapping.
Each operator converts either integers to strings or strings to integers, depending on which default value attribute is provided. Only one default value attribute should be defined.
If the string default value is set, it will convert integers to strings. If the int default value is set, it will convert strings to integers. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( CategoryMapper, 1, OpSchema() .SetDoc(CategoryMapper_ver1_doc) .Input(0, "X", "Input data", "T1") .Output( 0, "Y", "Output data. If strings are input, the output values are integers, and vice versa.", "T2") .TypeConstraint( "T1", {"tensor(string)", "tensor(int64)"}, "The input must be a tensor of strings or integers, either [N,C] or [C].") .TypeConstraint( "T2", {"tensor(string)", "tensor(int64)"}, "The output is a tensor of strings or integers. Its shape will be the same as the input shape.") .Attr( "cats_strings", "The strings of the map. This sequence must be the same length as the 'cats_int64s' sequence", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "cats_int64s", "The integers of the map. This sequence must be the same length as the 'cats_strings' sequence.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "default_string", "A string to use when an input integer value is not found in the map.
One and only one of the 'default_*' attributes must be defined.", AttributeProto::STRING, std::string("_Unused")) .Attr( "default_int64", "An integer to use when an input string value is not found in the map.
One and only one of the 'default_*' attributes must be defined.", AttributeProto::INT, static_cast(-1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto input_elem_type = ctx.getInputType(0)->tensor_type().elem_type(); auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); if (TensorProto::STRING == input_elem_type) { output_elem_type->set_elem_type(TensorProto::INT64); } else if (TensorProto::INT64 == input_elem_type) { output_elem_type->set_elem_type(TensorProto::STRING); } })); static const char* DictVectorizer_ver1_doc = R"DOC( Uses an index mapping to convert a dictionary to an array.
Given a dictionary, each key is looked up in the vocabulary attribute corresponding to the key type. The index into the vocabulary array at which the key is found is then used to index the output 1-D tensor 'Y' and insert into it the value found in the dictionary 'X'.
The key type of the input map must correspond to the element type of the defined vocabulary attribute. Therefore, the output array will be equal in length to the index mapping vector parameter. All keys in the input dictionary must be present in the index mapping vector. For each item in the input dictionary, insert its value in the output array. Any keys not present in the input dictionary, will be zero in the output array.
For example: if the ``string_vocabulary`` parameter is set to ``["a", "c", "b", "z"]``, then an input of ``{"a": 4, "c": 8}`` will produce an output of ``[4, 8, 0, 0]``. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( DictVectorizer, 1, OpSchema() .SetDoc(DictVectorizer_ver1_doc) .Input(0, "X", "A dictionary.", "T1") .Output( 0, "Y", "A 1-D tensor holding values from the input dictionary.", "T2") .TypeConstraint( "T1", {"map(string, int64)", "map(int64, string)", "map(int64, float)", "map(int64, double)", "map(string, float)", "map(string, double)"}, "The input must be a map from strings or integers to either strings or a numeric type. The key and value types cannot be the same.") .TypeConstraint( "T2", {"tensor(int64)", "tensor(float)", "tensor(double)", "tensor(string)"}, "The output will be a tensor of the value type of the input map. It's shape will be [1,C], where C is the length of the input dictionary.") .Attr( "string_vocabulary", "A string vocabulary array.
One and only one of the vocabularies must be defined.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "int64_vocabulary", "An integer vocabulary array.
One and only one of the vocabularies must be defined.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto input_elem_type = ctx.getInputType(0) ->map_type() .value_type() .tensor_type() .elem_type(); auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); output_elem_type->set_elem_type(input_elem_type); })); static const char* FeatureVectorizer_ver1_doc = R"DOC( Concatenates input tensors into one continuous output.
All input shapes are 2-D and are concatenated along the second dimention. 1-D tensors are treated as [1,C]. Inputs are copied to the output maintaining the order of the input arguments.
All inputs must be integers or floats, while the output will be all floating point values. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( FeatureVectorizer, 1, OpSchema() .SetDoc(FeatureVectorizer_ver1_doc) .Input( 0, "X", "An ordered collection of tensors, all with the same element type.", "T1", OpSchema::Variadic) .Output( 0, "Y", "The output array, elements ordered as the inputs.", "tensor(float)") .TypeConstraint( "T1", {"tensor(int32)", "tensor(int64)", "tensor(float)", "tensor(double)"}, "The input type must be a tensor of a numeric type.") .Attr( "inputdimensions", "The size of each input in the input list", AttributeProto::INTS, OPTIONAL_VALUE)); static const char* Imputer_ver1_doc = R"DOC( Replaces inputs that equal one value with another, leaving all other elements alone.
This operator is typically used to replace missing values in situations where they have a canonical representation, such as -1, 0, NaN, or some extreme value.
One and only one of imputed_value_floats or imputed_value_int64s should be defined -- floats if the input tensor holds floats, integers if the input tensor holds integers. The imputed values must all fit within the width of the tensor element type. One and only one of the replaced_value_float or replaced_value_int64 should be defined, which one depends on whether floats or integers are being processed.
The imputed_value attribute length can be 1 element, or it can have one element per input feature.
In other words, if the input tensor has the shape [*,F], then the length of the attribute array may be 1 or F. If it is 1, then it is broadcast along the last dimension and applied to each feature. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( Imputer, 1, OpSchema() .SetDoc(Imputer_ver1_doc) .Input(0, "X", "Data to be processed.", "T") .Output(0, "Y", "Imputed output data", "T") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input type must be a tensor of a numeric type, either [N,C] or [C]. The output type will be of the same tensor type and shape.") .Attr( "imputed_value_floats", "Value(s) to change to", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "replaced_value_float", "A value that needs replacing.", AttributeProto::FLOAT, 0.f) .Attr( "imputed_value_int64s", "Value(s) to change to.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "replaced_value_int64", "A value that needs replacing.", AttributeProto::INT, static_cast(0))); static const char* LabelEncoder_ver2_doc = R"DOC( Maps each element in the input tensor to another value.
The mapping is determined by the two parallel attributes, 'keys_*' and 'values_*' attribute. The i-th value in the specified 'keys_*' attribute would be mapped to the i-th value in the specified 'values_*' attribute. It implies that input's element type and the element type of the specified 'keys_*' should be identical while the output type is identical to the specified 'values_*' attribute. If an input element can not be found in the specified 'keys_*' attribute, the 'default_*' that matches the specified 'values_*' attribute may be used as its output value.
Let's consider an example which maps a string tensor to an integer tensor. Assume and 'keys_strings' is ["Amy", "Sally"], 'values_int64s' is [5, 6], and 'default_int64' is '-1'. The input ["Dori", "Amy", "Amy", "Sally", "Sally"] would be mapped to [-1, 5, 5, 6, 6].
Since this operator is an one-to-one mapping, its input and output shapes are the same. Notice that only one of 'keys_*'/'values_*' can be set.
For key look-up, bit-wise comparison is used so even a float NaN can be mapped to a value in 'values_*' attribute.
)DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( LabelEncoder, 2, OpSchema() .SetDoc(LabelEncoder_ver2_doc) .Input(0, "X", "Input data. It can be either tensor or scalar.", "T1") .Output(0, "Y", "Output data.", "T2") .TypeConstraint( "T1", {"tensor(string)", "tensor(int64)", "tensor(float)"}, "The input type is a tensor of any shape.") .TypeConstraint( "T2", {"tensor(string)", "tensor(int64)", "tensor(float)"}, "Output type is determined by the specified 'values_*' attribute.") .Attr( "keys_strings", "A list of strings. One and only one of 'keys_*'s should be set.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr("keys_int64s", "A list of ints.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "keys_floats", "A list of floats.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "values_strings", "A list of strings. One and only one of 'value_*'s should be set.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "values_int64s", "A list of ints.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "values_floats", "A list of floats.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "default_string", "A string.", AttributeProto::STRING, std::string("_Unused")) .Attr( "default_int64", "An integer.", AttributeProto::INT, static_cast(-1)) .Attr("default_float", "A float.", AttributeProto::FLOAT, -0.f) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Label encoder is one-to-one mapping. if (ctx.getNumInputs() != 1) fail_shape_inference("Label encoder has only one input."); if (ctx.getNumOutputs() != 1) fail_shape_inference("Label encoder has only one output."); // Load all key_* attributes. std::vector keys_strings; bool keys_strings_result = getRepeatedAttribute(ctx, "keys_strings", keys_strings); std::vector keys_int64s; bool keys_int64s_result = getRepeatedAttribute(ctx, "keys_int64s", keys_int64s); std::vector keys_floats; bool keys_floats_result = getRepeatedAttribute(ctx, "keys_floats", keys_floats); // Check if only one keys_* attribute is set. if (static_cast(keys_strings_result) + static_cast(keys_int64s_result) + static_cast(keys_floats_result) != 1) fail_shape_inference( "Only one of keys_*'s can be set in label encoder."); // Check if the specified keys_* matches input type. auto input_elem_type = ctx.getInputType(0)->tensor_type().elem_type(); if (keys_strings_result && input_elem_type != TensorProto::STRING) fail_shape_inference( "Input type is not string tensor but key_strings is set"); if (keys_int64s_result && input_elem_type != TensorProto::INT64) fail_shape_inference( "Input type is not int64 tensor but keys_int64s is set"); if (keys_floats_result && input_elem_type != TensorProto::FLOAT) fail_shape_inference( "Input type is not float tensor but keys_floats is set"); // Load all values_* attributes. std::vector values_strings; bool values_strings_result = getRepeatedAttribute(ctx, "values_strings", values_strings); std::vector values_int64s; bool values_int64s_result = getRepeatedAttribute(ctx, "values_int64s", values_int64s); std::vector values_floats; bool values_floats_result = getRepeatedAttribute(ctx, "values_floats", values_floats); // Check if only one values_* attribute is set. if (static_cast(values_strings_result) + static_cast(values_int64s_result) + static_cast(values_floats_result) != 1) fail_shape_inference( "Only one of values_*'s can be set in label encoder."); // Assign output type based on the specified values_*. auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); if (values_strings_result) output_elem_type->set_elem_type(TensorProto::STRING); if (values_int64s_result) output_elem_type->set_elem_type(TensorProto::INT64); if (values_floats_result) output_elem_type->set_elem_type(TensorProto::FLOAT); // Input and output shapes are the same. propagateShapeFromInputToOutput(ctx, 0, 0); })); static const char* LinearClassifier_ver1_doc = R"DOC( Linear classifier )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( LinearClassifier, 1, OpSchema() .SetDoc(LinearClassifier_ver1_doc) .Input(0, "X", "Data to be classified.", "T1") .Output(0, "Y", "Classification outputs (one class per example).", "T2") .Output( 1, "Z", "Classification scores ([N,E] - one score for each class and example", "tensor(float)") .TypeConstraint( "T1", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input must be a tensor of a numeric type, and of of shape [N,C] or [C]. In the latter case, it will be treated as [1,C]") .TypeConstraint( "T2", {"tensor(string)", "tensor(int64)"}, "The output will be a tensor of strings or integers.") .Attr( "coefficients", "A collection of weights of the model(s).", AttributeProto::FLOATS) .Attr( "intercepts", "A collection of intercepts.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "multi_class", "Indicates whether to do OvR or multinomial (0=OvR is the default).", AttributeProto::INT, static_cast(0)) .Attr( "classlabels_strings", "Class labels when using string labels. One and only one 'classlabels' attribute must be defined.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "classlabels_ints", "Class labels when using integer labels. One and only one 'classlabels' attribute must be defined.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "post_transform", "Indicates the transform to apply to the scores vector.
One of 'NONE,' 'SOFTMAX,' 'LOGISTIC,' 'SOFTMAX_ZERO,' or 'PROBIT'", AttributeProto::STRING, std::string("NONE")) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { std::vector label_strs; std::vector label_ints; auto labels_strings_present = getRepeatedAttribute(ctx, "classlabels_strings", label_strs); bool using_strings = (labels_strings_present && !label_strs.empty()); if (!using_strings) { getRepeatedAttribute(ctx, "classlabels_ints", label_ints); } // Type inference auto* output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); if (using_strings) { output_elem_type->set_elem_type(TensorProto::STRING); } else { output_elem_type->set_elem_type(TensorProto::INT64); } // second output is always of float type ctx.getOutputType(1)->mutable_tensor_type()->set_elem_type( TensorProto::FLOAT); // Shape/Rank inference begins // establish the number of classes std::vector intercepts; getRepeatedAttribute(ctx, "intercepts", intercepts); int class_count = static_cast(intercepts.size()); if (intercepts.size() == 1 && ((using_strings && label_strs.size() == 2) || (!using_strings && label_ints.size() == 2))) { class_count = 2; } TensorShapeProto_Dimension batch_size_dim, class_count_dim; class_count_dim.set_dim_value(class_count); if (hasNInputShapes(ctx, 1)) { const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto input_rank = input_shape.dim_size(); if (input_rank == 1) { // if input_rank is 1, batch_size is interpreted to be 1 batch_size_dim.set_dim_value(1); } else if (input_rank == 2) { batch_size_dim = input_shape.dim((int)0); } else { fail_shape_inference("Input's shape should be 1D or 2D"); } } updateOutputShape(ctx, 0, {batch_size_dim}); updateOutputShape(ctx, 1, {batch_size_dim, class_count_dim}); })); static const char* LinearRegressor_ver1_doc = R"DOC( Generalized linear regression evaluation.
If targets is set to 1 (default) then univariate regression is performed.
If targets is set to M then M sets of coefficients must be passed in as a sequence and M results will be output for each input n in N.
The coefficients array is of length n, and the coefficients for each target are contiguous. Intercepts are optional but if provided must match the number of targets. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( LinearRegressor, 1, OpSchema() .SetDoc(LinearRegressor_ver1_doc) .Input(0, "X", "Data to be regressed.", "T") .Output( 0, "Y", "Regression outputs (one per target, per example).", "tensor(float)") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input must be a tensor of a numeric type.") .Attr( "post_transform", "Indicates the transform to apply to the regression output vector.
One of 'NONE,' 'SOFTMAX,' 'LOGISTIC,' 'SOFTMAX_ZERO,' or 'PROBIT'", AttributeProto::STRING, std::string("NONE")) .Attr( "coefficients", "Weights of the model(s).", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "intercepts", "Weights of the intercepts, if used.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "targets", "The total number of regression targets, 1 if not defined.", AttributeProto::INT, static_cast(1))); static const char* Normalizer_ver1_doc = R"DOC( Normalize the input. There are three normalization modes, which have the corresponding formulas, defined using element-wise infix operators '/' and '^' and tensor-wide functions 'max' and 'sum':

Max: Y = X / max(X)
L1: Y = X / sum(X)
L2: Y = sqrt(X^2 / sum(X^2)}
In all modes, if the divisor is zero, Y == X.
For batches, that is, [N,C] tensors, normalization is done along the C axis. In other words, each row of the batch is normalized independently. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( Normalizer, 1, OpSchema() .SetDoc(Normalizer_ver1_doc) .Input( 0, "X", "Data to be encoded, a tensor of shape [N,C] or [C]", "T") .Output(0, "Y", "Encoded output data", "tensor(float)") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input must be a tensor of a numeric type.") .Attr( "norm", "One of 'MAX,' 'L1,' 'L2'", AttributeProto::STRING, std::string("MAX"))); static const char* OneHotEncoder_ver1_doc = R"DOC( Replace each input element with an array of ones and zeros, where a single one is placed at the index of the category that was passed in. The total category count will determine the size of the extra dimension of the output array Y.
For example, if we pass a tensor with a single value of 4, and a category count of 8, the output will be a tensor with ``[0,0,0,0,1,0,0,0]``.
This operator assumes every input feature is from the same set of categories.
If the input is a tensor of float, int32, or double, the data will be cast to integers and the cats_int64s category list will be used for the lookups. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( OneHotEncoder, 1, OpSchema() .SetDoc(OneHotEncoder_ver1_doc) .Input(0, "X", "Data to be encoded.", "T") .Output( 0, "Y", "Encoded output data, having one more dimension than X.", "tensor(float)") .TypeConstraint( "T", {"tensor(string)", "tensor(int64)", "tensor(int32)", "tensor(float)", "tensor(double)"}, "The input must be a tensor of a numeric type.") .Attr( "cats_int64s", "List of categories, ints.
One and only one of the 'cats_*' attributes must be defined.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "cats_strings", "List of categories, strings.
One and only one of the 'cats_*' attributes must be defined.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "zeros", "If true and category is not present, will return all zeros; if false and a category if not found, the operator will fail.", AttributeProto::INT, static_cast(1))); static const char* Scaler_ver1_doc = R"DOC( Rescale input data, for example to standardize features by removing the mean and scaling to unit variance. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( Scaler, 1, OpSchema() .SetDoc(Scaler_ver1_doc) .Input(0, "X", "Data to be scaled.", "T") .Output(0, "Y", "Scaled output data.", "tensor(float)") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input must be a tensor of a numeric type.") .Attr( "offset", "First, offset by this.
Can be length of features in an [N,F] tensor or length 1, in which case it applies to all features, regardless of dimension count.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "scale", "Second, multiply by this.
Can be length of features in an [N,F] tensor or length 1, in which case it applies to all features, regardless of dimension count.
Must be same length as 'offset'", AttributeProto::FLOATS, OPTIONAL_VALUE)); static const char* SVMClassifier_ver1_doc = R"DOC( Support Vector Machine classifier )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( SVMClassifier, 1, OpSchema() .SetDoc(SVMClassifier_ver1_doc) .Input(0, "X", "Data to be classified.", "T1") .Output(0, "Y", "Classification outputs (one class per example).", "T2") .Output( 1, "Z", "Class scores (one per class per example), if prob_a and prob_b are provided they are probabilities for each class, otherwise they are raw scores.", "tensor(float)") .TypeConstraint( "T1", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input must be a tensor of a numeric type, either [C] or [N,C].") .TypeConstraint( "T2", {"tensor(string)", "tensor(int64)"}, "The output type will be a tensor of strings or integers, depending on which of the the classlabels_* attributes is used. Its size will match the bactch size of the input.") .Attr( "kernel_type", "The kernel type, one of 'LINEAR,' 'POLY,' 'RBF,' 'SIGMOID'.", AttributeProto::STRING, std::string("LINEAR")) .Attr( "kernel_params", "List of 3 elements containing gamma, coef0, and degree, in that order. Zero if unused for the kernel.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr("vectors_per_class", "", AttributeProto::INTS, OPTIONAL_VALUE) .Attr("support_vectors", "", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr("coefficients", "", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "prob_a", "First set of probability coefficients.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "prob_b", "Second set of probability coefficients. This array must be same size as prob_a.
If these are provided then output Z are probability estimates, otherwise they are raw scores.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr("rho", "", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "post_transform", "Indicates the transform to apply to the score.
One of 'NONE,' 'SOFTMAX,' 'LOGISTIC,' 'SOFTMAX_ZERO,' or 'PROBIT'", AttributeProto::STRING, std::string("NONE")) .Attr( "classlabels_strings", "Class labels if using string labels.
One and only one of the 'classlabels_*' attributes must be defined.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "classlabels_ints", "Class labels if using integer labels.
One and only one of the 'classlabels_*' attributes must be defined.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { std::vector label_strs; auto result = getRepeatedAttribute(ctx, "classlabels_strings", label_strs); bool using_strings = (result && !label_strs.empty()); auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); if (using_strings) { output_elem_type->set_elem_type(TensorProto::STRING); } else { output_elem_type->set_elem_type(TensorProto::INT64); } })); static const char* SVMRegressor_ver1_doc = R"DOC( Support Vector Machine regression prediction and one-class SVM anomaly detection. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( SVMRegressor, 1, OpSchema() .SetDoc(SVMRegressor_ver1_doc) .Input(0, "X", "Data to be regressed.", "T") .Output( 0, "Y", "Regression outputs (one score per target per example).", "tensor(float)") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input type must be a tensor of a numeric type, either [C] or [N,C].") .Attr( "kernel_type", "The kernel type, one of 'LINEAR,' 'POLY,' 'RBF,' 'SIGMOID'.", AttributeProto::STRING, std::string("LINEAR")) .Attr( "kernel_params", "List of 3 elements containing gamma, coef0, and degree, in that order. Zero if unused for the kernel.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "support_vectors", "Chosen support vectors", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "one_class", "Flag indicating whether the regression is a one-class SVM or not.", AttributeProto::INT, static_cast(0)) .Attr( "coefficients", "Support vector coefficients.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "n_supports", "The number of support vectors.", AttributeProto::INT, static_cast(0)) .Attr( "post_transform", "Indicates the transform to apply to the score.
One of 'NONE,' 'SOFTMAX,' 'LOGISTIC,' 'SOFTMAX_ZERO,' or 'PROBIT.'", AttributeProto::STRING, std::string("NONE")) .Attr("rho", "", AttributeProto::FLOATS, OPTIONAL_VALUE)); static const char* TreeEnsembleClassifier_ver1_doc = R"DOC( Tree Ensemble classifier. Returns the top class for each of N inputs.
The attributes named 'nodes_X' form a sequence of tuples, associated by index into the sequences, which must all be of equal length. These tuples define the nodes.
Similarly, all fields prefixed with 'class_' are tuples of votes at the leaves. A leaf may have multiple votes, where each vote is weighted by the associated class_weights index.
One and only one of classlabels_strings or classlabels_int64s will be defined. The class_ids are indices into this list. )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( TreeEnsembleClassifier, 1, OpSchema() .SetDoc(TreeEnsembleClassifier_ver1_doc) .Input(0, "X", "Input of shape [N,F]", "T1") .Output(0, "Y", "N, Top class for each point", "T2") .Output( 1, "Z", "The class score for each class, for each point, a tensor of shape [N,E].", "tensor(float)") .TypeConstraint( "T1", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input type must be a tensor of a numeric type.") .TypeConstraint( "T2", {"tensor(string)", "tensor(int64)"}, "The output type will be a tensor of strings or integers, depending on which of the the classlabels_* attributes is used.") .Attr( "nodes_treeids", "Tree id for each node.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_nodeids", "Node id for each node. Ids may restart at zero for each tree, but it not required to.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_featureids", "Feature id for each node.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_values", "Thresholds to do the splitting on for each node.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "nodes_hitrates", "Popularity of each node, used for performance and may be omitted.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "nodes_modes", "The node kind, that is, the comparison to make at the node. There is no comparison to make at a leaf node.
One of 'BRANCH_LEQ', 'BRANCH_LT', 'BRANCH_GTE', 'BRANCH_GT', 'BRANCH_EQ', 'BRANCH_NEQ', 'LEAF'", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "nodes_truenodeids", "Child node if expression is true.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_falsenodeids", "Child node if expression is false.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_missing_value_tracks_true", "For each node, define what to do in the presence of a missing value: if a value is missing (NaN), use the 'true' or 'false' branch based on the value in this array.
This attribute may be left undefined, and the defalt value is false (0) for all nodes.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "class_treeids", "The id of the tree that this node is in.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "class_nodeids", "node id that this weight is for.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "class_ids", "The index of the class list that each weight is for.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "class_weights", "The weight for the class in class_id.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "classlabels_strings", "Class labels if using string labels.
One and only one of the 'classlabels_*' attributes must be defined.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "classlabels_int64s", "Class labels if using integer labels.
One and only one of the 'classlabels_*' attributes must be defined.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "post_transform", "Indicates the transform to apply to the score.
One of 'NONE,' 'SOFTMAX,' 'LOGISTIC,' 'SOFTMAX_ZERO,' or 'PROBIT.'", AttributeProto::STRING, std::string("NONE")) .Attr( "base_values", "Base values for classification, added to final class score; the size must be the same as the classes or can be left unassigned (assumed 0)", AttributeProto::FLOATS, OPTIONAL_VALUE) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { std::vector label_strs; auto result = getRepeatedAttribute(ctx, "classlabels_strings", label_strs); bool using_strings = (result && !label_strs.empty()); auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); if (using_strings) { output_elem_type->set_elem_type(TensorProto::STRING); } else { output_elem_type->set_elem_type(TensorProto::INT64); } })); static const char* TreeEnsembleRegressor_ver1_doc = R"DOC( Tree Ensemble regressor. Returns the regressed values for each input in N.
All args with nodes_ are fields of a tuple of tree nodes, and it is assumed they are the same length, and an index i will decode the tuple across these inputs. Each node id can appear only once for each tree id.
All fields prefixed with target_ are tuples of votes at the leaves.
A leaf may have multiple votes, where each vote is weighted by the associated target_weights index.
All trees must have their node ids start at 0 and increment by 1.
Mode enum is BRANCH_LEQ, BRANCH_LT, BRANCH_GTE, BRANCH_GT, BRANCH_EQ, BRANCH_NEQ, LEAF )DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( TreeEnsembleRegressor, 1, OpSchema() .SetDoc(TreeEnsembleRegressor_ver1_doc) .Input(0, "X", "Input of shape [N,F]", "T") .Output(0, "Y", "N classes", "tensor(float)") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int64)", "tensor(int32)"}, "The input type must be a tensor of a numeric type.") .Attr( "nodes_treeids", "Tree id for each node.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_nodeids", "Node id for each node. Node ids must restart at zero for each tree and increase sequentially.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_featureids", "Feature id for each node.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_values", "Thresholds to do the splitting on for each node.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "nodes_hitrates", "Popularity of each node, used for performance and may be omitted.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "nodes_modes", "The node kind, that is, the comparison to make at the node. There is no comparison to make at a leaf node.
One of 'BRANCH_LEQ', 'BRANCH_LT', 'BRANCH_GTE', 'BRANCH_GT', 'BRANCH_EQ', 'BRANCH_NEQ', 'LEAF'", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "nodes_truenodeids", "Child node if expression is true", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_falsenodeids", "Child node if expression is false", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "nodes_missing_value_tracks_true", "For each node, define what to do in the presence of a NaN: use the 'true' (if the attribute value is 1) or 'false' (if the attribute value is 0) branch based on the value in this array.
This attribute may be left undefined and the defalt value is false (0) for all nodes.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "target_treeids", "The id of the tree that each node is in.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "target_nodeids", "The node id of each weight", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "target_ids", "The index of the target that each weight is for", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "target_weights", "The weight for each target", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "n_targets", "The total number of targets.", AttributeProto::INT, OPTIONAL_VALUE) .Attr( "post_transform", "Indicates the transform to apply to the score.
One of 'NONE,' 'SOFTMAX,' 'LOGISTIC,' 'SOFTMAX_ZERO,' or 'PROBIT'", AttributeProto::STRING, std::string("NONE")) .Attr( "aggregate_function", "Defines how to aggregate leaf values within a target.
One of 'AVERAGE,' 'SUM,' 'MIN,' 'MAX.'", AttributeProto::STRING, std::string("SUM")) .Attr( "base_values", "Base values for classification, added to final class score; the size must be the same as the classes or can be left unassigned (assumed 0)", AttributeProto::FLOATS, OPTIONAL_VALUE)); static const char* ZipMap_ver1_doc = R"DOC( Creates a map from the input and the attributes.
The values are provided by the input tensor, while the keys are specified by the attributes. Must provide keys in either classlabels_strings or classlabels_int64s (but not both).
The columns of the tensor correspond one-by-one to the keys specified by the attributes. There must be as many columns as keys.
)DOC"; ONNX_ML_OPERATOR_SET_SCHEMA( ZipMap, 1, OpSchema() .SetDoc(ZipMap_ver1_doc) .Input(0, "X", "The input values", "tensor(float)") .Output(0, "Z", "The output map", "T") .TypeConstraint( "T", {"seq(map(string, float))", "seq(map(int64, float))"}, "The output will be a sequence of string or integer maps to float.") .Attr( "classlabels_strings", "The keys when using string keys.
One and only one of the 'classlabels_*' attributes must be defined.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "classlabels_int64s", "The keys when using int keys.
One and only one of the 'classlabels_*' attributes must be defined.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { std::vector classlabels_strings; bool result = getRepeatedAttribute( ctx, "classlabels_strings", classlabels_strings); auto output_map_type = ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_map_type(); output_map_type->mutable_value_type() ->mutable_tensor_type() ->set_elem_type(TensorProto::FLOAT); if (result && !classlabels_strings.empty()) { output_map_type->set_key_type(TensorProto::STRING); } std::vector classlabels_int64s; result = getRepeatedAttribute( ctx, "classlabels_int64s", classlabels_int64s); if (result && !classlabels_int64s.empty()) { output_map_type->set_key_type(TensorProto::INT64); } })); } // namespace ONNX_NAMESPACE #endif onnx-1.7.0/onnx/defs/training/0000775000000000000000000000000013655345213014726 5ustar rootrootonnx-1.7.0/onnx/defs/training/defs.cc0000664000000000000000000007745013655345213016173 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include "onnx/defs/function.h" #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { static const char* Gradient_ver1_doc = R"DOC( Gradient operator computes the partial derivatives of a specific tensor w.r.t. some other tensors. This operator is widely used in gradient-based training algorithms. To illustrate its use, let's consider a computation graph, ``` X -----. | v W --> Conv --> H --> Gemm --> Y ^ | Z ``` , where W and Z are trainable tensors. Note that operators' attributes are omitted for the sake of simplicity. Let dY/dW (dY/dZ) be the gradient of Y with respect to W (Z). The user can compute gradient by inserting Gradient operator to form another graph shown below. ``` W --> Conv --> H --> Gemm --> Y | ^ ^ | | | | X Z | | | | | .----------' | | | (W/Z/X is the 1st/2nd/3rd input of Gradient as shown in | | | "xs" followed by "zs") | v v '---> Gradient(xs=["W", "Z"], zs=["X"], y="Y") | | | '-----------------------------------> dY/dW (1st output of Gradient) | '---------------------------------------> dY/dZ (2nd output of Gradient) ``` By definition, the tensor "y" is a function of independent variables in "xs" and "zs". Since we only compute the gradient of "y" w.r.t. the differentiable variables in "xs", this Gradient only outputs dY/dW and dY/dZ. Note that "H" cannot appear in "xs" and "zs". The reason is that "H" can be determined by tensors "W" and "X" and therefore "H" is not an independent variable. All outputs are optional. If needed, for example, user can assign an empty string to the 1st output name of that Gradient to skip the generation of dY/dW. Note that the concept of optional outputs can also be found in ONNX's RNN, GRU, and LSTM. Gradient operator can compute derivative against intermediate tensors. For example, the gradient of Y with respect to H can be done via ``` W --> Conv --> H --> Gemm --> Y ^ | ^ | | | X | Z .-------' | | .----------' | | (H/Z is the 1st/2nd input of Gradient as shown in "xs") v v Gradient(xs=["H", "Z"], y="Y") | | | '-----------------------------------> dY/dH (1st output of Gradient) | '---------------------------------------> dY/dZ (2nd output of Gradient) ``` It is possible to represent high-order differentiation using Gradient operators. For example, given the following linear model: ``` W --> Gemm --> Y --> Loss --> O ^ ^ | | X L ``` To compute the 2nd order derivative of O with respect to W (denoted by d^2O/dW^2), one can do ``` W --> Gemm --> Y --> Loss --> O | ^ ^ | | | | X .------------L | | | | | | | v +------+-+> Gradient(xs=["X", "W"], zs=["L"], y="O") ---> dO/dX (1st output of Gradient) | | | | | | | '---> dO/dW (2nd output of Gradient) | v v '---> Gradient(xs=["X", "W"], zs=["L"], y="dO/dW") ---> d(dO/dW)dX (1st output of | Gradient) | | '---> d^2O/dW^2 (2nd output of Gradient) ``` The tensors named in attributes "xs", "zs", and "y" define the differentiated computation graph, and the inputs to Gradient node define the values at which the gradient is computed. We can feed different tensors to the identified graph. For example, one can compute the gradient of Y with respect to H at a specific value of H, H_1, by providing that value as an input to the Gradient node. ``` W --> Conv --> H --> Gemm --> Y ^ ^ | | X Z Z_1 (2nd input of Gradient) | v H_1 --> Gradient(xs=["H", "Z"], y="Y") ---> dY/dH when H = H_1 and Y = Y_1. | '------------------------------> dY/dZ (2nd output of Gradient) ``` When the inputs of Gradient are the tensors named in "xs" and "zs", the computation can be optimized. More specifically, intermediate variables in forward pass can be reused if the gradient is computed via reverse-mode auto-differentiation. )DOC"; ONNX_PREVIEW_TRAINING_OPERATOR_SET_SCHEMA( Gradient, 1, OpSchema() .SetDoc(Gradient_ver1_doc) .Input( 0, "Inputs", "The values fed into graph identified by the attributes. " "The i-th input is the value of the i-th tensor specified in the " "concatenated list of the attribute \"xs\" and the attribute " " \"zs\". For example, if xs=[\"A\", \"B\"] and zs=[\"C\"], the " "first input is used as the value of symbol \"A\" and the 3rd " "input is substituted for all the occurrences of \"C\".", "T1", OpSchema::Variadic, false) .Output( 0, "Outputs", "The gradient of the tensor specified by the attribute \"y\" " "with respect to each of tensors specified in the " "attribute \"xs\". The i-th output is the gradient of \"y\" with " "respect to the i-th tensor specified in the attribute \"xs\".", "T2", OpSchema::Variadic, false) .Attr( "xs", "Input tensor names of the differentiated sub-graph. It " "contains only the necessary differentiated " "inputs of a (sub-)graph. Variables (usually called " "intermediate variables) that can be generated from inputs " "cannot be included in this attribute.", AttributeProto::STRINGS) .Attr( "zs", "Input tensor names of the differentiated sub-graph. It " "contains only the necessary non-differentiated " "inputs of a (sub-)graph. Variables (usually called " "intermediate variables) that can be generated from inputs " "cannot be included in this attribute.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "y", "The targeted tensor. It can be viewed as the output of the " "differentiated function. The attribute \"xs\" and attribute " "\"zs\" are the minimal independent variable set that determines " "the value of \"y\".", AttributeProto::STRING) .TypeConstraint( "T1", OpSchema::all_tensor_types(), "Allow outputs to be any kind of tensor.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Allow inputs to be any kind of floating-point tensor.")); static const char* GraphCall_ver1_doc = R"DOC( The GraphCall operator invokes a graph inside TrainingInfoProto's algorithm field. The GraphCall inputs and outputs are bound to those of invoked graph by position. If a graph input has an initializer, that input is considered optional. All graph outputs are optional. Below Python syntax is used for describing dictionary and list. Assume that ModelProto's graph field has - name: "MyInferenceGraph" - input: ["X", "W", "Z"] - initializer: [W] - output: ["Y"] as visualized below for inference. ``` X -----. | v W --> Conv --> H --> Gemm --> Y ^ | Z ``` Assume that the training algorithm contains - inputs: ["X_1", "Z_1", "C"] - initializer: [T] - outputs: ["W_new"] with a dictionary - update_binding: {"W": "W_new", "T": "T_new"} Inside the training algorithm graph, one can invoke the inference graph via adding a GraphCall node with - inputs: ["X_1", "W", Z_1"] - outputs: ["Y_1"] - an attribute graph_name="MyInferenceGraph", The initializers, "W" and "T" in this case, in update_binding are considered globally-visible and mutable variables, which can be used as inputs of operators in the training graph. An example training algorithm graph may look like ``` .-------- W (a global and mutable variable from | | the inference graph) | | | .-----'-----------. | | | | | v | | .-- X_1 --> GraphCall(graph_name="MyInferenceGraph") | | | | | | | | | | | | | Z_1 -----' | | | | | V | | | | Y_1 ---> Loss ---> O | | | | ^ | | | | | | | `--. | C | | | | | | | | | .----------------' | | | | | | | v v v | `--> Gradient(xs=["W"], zs=["X_1", "Z_1", "C"], y="O") | | | v | dO_dW (gradient of W) 1 (a scalar one) | | | | V v | Div <--- T ------------> Add ---> T_new | | (T is the number of training iterations. | | T is also globally visible and mutable.) | v `-----> Sub ----> W_new ``` where Loss is a dummy node which computes the minimized objective function. The variable "W" is an optional input in the called graph. If the user omits it, the input list of GraphCall becomes ["X_1", "", "Z_1"]. In this case, from the view of computation graph, the Conv operator invoked by GraphCall's may be still connected the global "W" variable and therefore the structure of the computation graph is unchanged. )DOC"; ONNX_PREVIEW_TRAINING_OPERATOR_SET_SCHEMA( GraphCall, 1, OpSchema() .SetDoc(GraphCall_ver1_doc) .Input( 0, "Inputs", "Inputs fed to the invoked graph. " "The i-th input here goes to the i-th input of the invoked graph. " "To omit an optional input in this field, " "the user can drop it or use an empty string.", "T", OpSchema::Variadic, false) .Output( 0, "Outputs", "The outputs generated by the called graph. " "Its i-th value is bound to the i-th output of the called graph. " "Similar to the inputs, all outputs are optional.", "T", OpSchema::Variadic, false) .Attr( "graph_name", "The invoked graph's name. " "The only allowed value is the name of the inference graph, " "which is stored in \"ModelProto.graph.name\" " "in the ONNX model format.", AttributeProto::STRING) .TypeConstraint( "T", OpSchema::all_tensor_types(), "Allow inputs and outputs to be any kind of tensor.")); static const char* Adagrad_ver1_doc = R"DOC( Compute one iteration of ADAGRAD, a stochastic gradient based optimization algorithm. This operator can conduct the optimization of multiple tensor variables. Let's define the behavior of this operator. As you can imagine, ADAGRAD requires some parameters: - The initial learning-rate "R". - The update count "T". That is, the number of training iterations conducted. - A L2-norm regularization coefficient "norm_coefficient". - A learning-rate decay factor "decay_factor". - A small constant "epsilon" to avoid dividing-by-zero. At each ADAGRAD iteration, the optimized tensors are moved along a direction computed based on their estimated gradient and accumulated squared gradient. Assume that only a single tensor "X" is updated by this operator. We need the value of "X", its gradient "G", and its accumulated squared gradient "H". Therefore, variables in this operator's input list are sequentially "R", "T", "X", "G", and "H". Other parameters are given as attributes because they are usually constants. Also, the corresponding output tensors are the new value of "X" (called "X_new"), and then the new accumulated squared gradient (called "H_new"). Those outputs are computed from the given inputs following the pseudo code below. Let "+", "-", "*", and "/" are all element-wise arithmetic operations with numpy-style broadcasting support. The pseudo code to compute those outputs is: // Compute a scalar learning-rate factor. At the first update of X, T is generally // 0 (0-based update index) or 1 (1-based update index). r = R / (1 + T * decay_factor); // Add gradient of 0.5 * norm_coefficient * ||X||_2^2, where ||X||_2 is the 2-norm. G_regularized = norm_coefficient * X + G; // Compute new accumulated squared gradient. H_new = H + G_regularized * G_regularized; // Compute the adaptive part of per-coordinate learning rate. Note that Sqrt(...) // computes element-wise square-root. H_adaptive = Sqrt(H_new) + epsilon // Compute the new value of "X". X_new = X - r * G_regularized / H_adaptive; If one assign this operators to optimize multiple inputs, for example, "X_1" and "X_2", the same pseudo code may be extended to handle all tensors jointly. More specifically, we can view "X" as a concatenation of "X_1" and "X_2" (of course, their gradient and accumulate gradient should be concatenated too) and then just reuse the entire pseudo code. Note that ADAGRAD was first proposed in http://jmlr.org/papers/volume12/duchi11a/duchi11a.pdf. In that reference paper, this operator is a special case of the Figure 1's composite mirror descent update. )DOC"; ONNX_PREVIEW_TRAINING_OPERATOR_SET_SCHEMA( Adagrad, 1, OpSchema() .SetDoc(Adagrad_ver1_doc) .Input(0, "R", "The initial learning rate.", "T1") .Input(1, "T", "The update count of \"X\". It should be a scalar.", "T2") .Input( 2, "inputs", "The current values of optimized tensors, followed by their " "respective gradients, followed by their respective accumulated squared gradients." "For example, if two tensor \"X_1\" and \"X_2\" " "are optimized, " "The input list would be " "[\"X_1\", \"X_2\", " "gradient of \"X_1\", " "gradient of \"X_2\", " "accumulated squared gradient of \"X_1\", " "accumulated squared gradient of \"X_2\"].", "T3", OpSchema::Variadic, false) .Output( 0, "outputs", "Updated values of optimized tensors, followed by their updated " "values of accumulated squared gradients. For example, " "if two tensor \"X_1\" and \"X_2\" are " "optimized, the output list would be [new value of \"X_1,\" new value of \"X_2\" " "new accumulated squared gradient of \"X_1\", new accumulated squared gradient of \"X_2\"].", "T3", OpSchema::Variadic, false) .Attr( "epsilon", "Small scalar to avoid dividing by zero.", AttributeProto::FLOAT, 1e-6f) .Attr( "decay_factor", "The decay factor of learning rate after one update." "The effective learning rate is computed by r = R / (1 + T * decay_factor). " "Default to 0 so that increasing update counts doesn't reduce the learning rate.", AttributeProto::FLOAT, 0.0f) .Attr( "norm_coefficient", "Regularization coefficient in 0.5 * norm_coefficient * ||X||_2^2. Default to 0, " "which means no regularization.", AttributeProto::FLOAT, 0.0f) .TypeConstraint( "T1", {"tensor(float)", "tensor(double)"}, "Constrain input types to float scalars.") .TypeConstraint( "T2", {"tensor(int64)"}, "Constrain input types to 64-bit integer scalars.") .TypeConstraint( "T3", {"tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // In comments below, we assume that the input list is // [R, T, X1, X2, G1, G2, H1, H2] and the output list is // [X1_new, X2_new, H1_new, H2_new]. // Compute the number of tuples (X, G, H). auto num_optimized_tensors = (ctx.getNumInputs() - 2) / 3; for (size_t i = 0; i < num_optimized_tensors; ++i) { // Pass X1's and X2's shapes to X1_new and X2_new, respectively. size_t i_in = 2 + i; size_t i_out = i; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); // Pass H1's and H2's shapes to H1_new and H2_new, respectively. i_in = 2 + 2 * num_optimized_tensors + i; i_out = i + num_optimized_tensors; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); }})); static const char* Momentum_ver1_doc = R"DOC( Compute one iteration of stochastic gradient update with momentum. This operator can conduct the optimization of multiple tensor variables. Let's define the behavior of this operator. As you can imagine, SG with momentum requires several parameters: - The learning-rate "R". - The update count "T". That is, the number of conducted training iterations. It should be zero in the first training iteration. - A L2-norm regularization coefficient "norm_coefficient". - A decay coefficient of previous accumulated gradient (i.e., momentum) "alpha". - The scaling coefficient of current gradient "beta". - An attribute to choose either standard momentum or Nesterov's momentum "mode" should be used. For the sake of simplicity, assume that there is only one tensor (called "X") to be optimized. Other necessary inputs are "X"'s gradient (called "G") and "X"'s momentum (called "V"). This Momentum operator maps all these inputs to the new value of "X" (called "X_new") and its new momentum (called "V_new"). This operator supports two different momentum algorithms. Set the attribute "mode" to "nesterov" if Nesterov's momentum is desired. Otherwise, set the attribute "model" to "standard" to use standard momentum. Computation details are described subsequently. Let "+", "-", "*", and "/" are all element-wise operations with numpy-style broadcasting. Pseudo code for SG with standard momentum: // Add gradient of 0.5 * norm_coefficient * ||X||^2, where ||X|| is the sum of squared // values of all elements in X. G_regularized = norm_coefficient * X + G // In the first training iteration, beta should always be 1. beta_adjusted = T > 0 ? beta : 1 // Compute the current momentum based on previous momentum and the current gradient. V_new = alpha * V + beta_adjusted * G_regularized // Update X. X_new = X - R * V_new Pseudo code for SG with Nesterov's momentum: // Add gradient of 0.5 * norm_coefficient * ||X||^2, where ||X|| is the sum of squared // values of all elements in X. G_regularized = norm_coefficient * X + G; // In the first training iteration, beta should always be 1. beta_adjusted = T > 0 ? beta : 1 // Compute the current momentum based on previous momentum and the current gradient. V_new = alpha * V + beta_adjusted * G_regularized; // Compute final update direction and then update X. X_new = X - R * (G_regularized + alpha * V_new) If one assign this operators to optimize multiple inputs, for example, "X_1" and "X_2". The same pseudo code would be extended to handle all tensors jointly. More specifically, we can view "X" as a concatenation of "X_1" and "X_2" (of course, their gradient and accumulate gradient should be concatenated too) and then our pseudo code becomes applicable. )DOC"; ONNX_PREVIEW_TRAINING_OPERATOR_SET_SCHEMA( Momentum, 1, OpSchema() .SetDoc(Momentum_ver1_doc) .Input(0, "R", "The learning rate.", "T1") .Input(1, "T", "Update count of \"X\". It should be a scalar.", "T2") .Input( 2, "inputs", "It sequentially contains the current values of optimized tensors, then their " "gradient tensors, and finally their momentum tensors. For example, if two tensors " "\"X_1\" and \"X_2\" are optimized, The expected input list would be " "[\"X_1\", \"X_2\", gradient of \"X_1\", gradient of \"X_2\", momentum of \"X_1\", momentum of \"X_2\"].", "T3", OpSchema::Variadic, false) .Output( 0, "outputs", "It sequentially contains the new values of optimized tensors and then the new " "values of their momentum tensors. For example, if two tensors \"X_1\" and \"X_2\" are " "optimized, the output list would be [new value of \"X_1,\" new value of \"X_2\" " "new momentum of \"X_1\", new momentum of \"X_2\"].", "T3", OpSchema::Variadic, false) .Attr( "alpha", "The decay factor of momentum. It should be a scalar.", AttributeProto::FLOAT) .Attr( "beta", "The coefficient of gradient in computing new momentum. It should be a scalar.", AttributeProto::FLOAT) .Attr( "norm_coefficient", "Coefficient of 0.5 * norm_coefficient * ||X||^2.", AttributeProto::FLOAT) .Attr( "mode", "Its value should be either \"nesterov\" or \"standard\". The value \"nesterov\" leads " "to the use of Nesterov's momentum while \"standard\" invokes stochastic gradient method " "using standard momentum", AttributeProto::STRING) .TypeConstraint( "T1", {"tensor(float)", "tensor(double)"}, "Constrain input types to float scalars.") .TypeConstraint( "T2", {"tensor(int64)"}, "Constrain input types to 64-bit integer scalars.") .TypeConstraint( "T3", {"tensor(float)", "tensor(double)"}, "Constrain input types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Assume that the input list is [R, T, X1, X2, G1, G2, V1, V2] and // output list is [X1_new, X2_new, V1_new, V2_new] for explaining // the code below in a simpler way. // The count of input tensors excluding "R" and "T". auto num_adjustable_tensors = ctx.getNumInputs() - 2; // Check number of (optimized tensor, gradient, momentum) tuples. if (num_adjustable_tensors % 3 != 0) fail_shape_inference( "The sum of optimized tensor count and momentum tensor count ", "should be a multiple of 2 in the input list of Momentum operator"); // The count of "X1" and "X2". auto num_optimized_tensors = num_adjustable_tensors / 3; for (size_t i = 0; i < num_optimized_tensors; ++i){ // Pass X1's/X2's shapes to X1_new/X2_new. size_t i_in = 2 + i; size_t i_out = i; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); // Pass V1's/V2's shapes to V1_new/V2_new. i_in = 2 + 2 * num_optimized_tensors + i; i_out = i + num_optimized_tensors; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); } })); static const char* Adam_ver1_doc = R"DOC( Compute one iteration of Adam, a stochastic gradient based optimization algorithm. This operator can conduct the optimization of multiple tensor variables. Let's define the behavior of this operator. First of all, Adam requires some parameters: - The learning-rate "R". - The update count "T". That is, the number of training iterations conducted. - A L2-norm regularization coefficient "norm_coefficient". - A small constant "epsilon" to avoid dividing-by-zero. - Two coefficients, "alpha" and "beta". At each Adam iteration, the optimized tensors are moved along a direction computed based on their exponentially-averaged historical gradient and exponentially-averaged historical squared gradient. Assume that only a tensor "X" is being optimized. The rest of required information is - the value of "X", - "X"'s gradient (denoted by "G"), - "X"'s exponentially-averaged historical gradient (denoted by "V"), and - "X"'s exponentially-averaged historical squared gradient (denoted by "H"). Some of those parameters are passed into this operator as input tensors and others are stored as this operator's attributes. Specifically, this operator's input tensor list is ["R", "T", "X", "G", "V", "H"]. That is, "R" is the first input, "T" is the second input, and so on. Other parameters are given as attributes because they are constants. Moreover, the corresponding output tensors are - the new value of "X" (called "X_new"), - the new exponentially-averaged historical gradient (denoted by "V_new"), and - the new exponentially-averaged historical squared gradient (denoted by "H_new"). Those outputs are computed following the pseudo code below. Let "+", "-", "*", and "/" are all element-wise arithmetic operations with numpy-style broadcasting support. The pseudo code to compute those outputs is: // Add gradient of 0.5 * norm_coefficient * ||X||_2^2, where ||X||_2 is the 2-norm. G_regularized = norm_coefficient * X + G // Update exponentially-averaged historical gradient. V_new = alpha * V + (1 - alpha) * G_regularized // Update exponentially-averaged historical squared gradient. H_new = beta * H + (1 - beta) * G_regularized * G_regularized // Compute the element-wise square-root of H_new. V_new will be element-wisely // divided by H_sqrt for a better update direction. H_sqrt = Sqrt(H_new) + epsilon // Compute learning-rate. Note that "alpha**T"/"beta**T" is alpha's/beta's T-th power. R_adjusted = T > 0 ? R * Sqrt(1 - beta**T) / (1 - alpha**T) : R // Compute new value of "X". X_new = X - R_adjusted * V_new / H_sqrt // Post-update regularization. X_final = (1 - norm_coefficient_post) * X_new If there are multiple inputs to be optimized, the pseudo code will be applied independently to each of them. )DOC"; ONNX_PREVIEW_TRAINING_OPERATOR_SET_SCHEMA( Adam, 1, OpSchema() .SetDoc(Adam_ver1_doc) .Input(0, "R", "The initial learning rate.", "T1") .Input(1, "T", "The update count of \"X\". It should be a scalar.", "T2") .Input( 2, "inputs", "The tensors to be optimized, followed by their respective gradients, " "followed by their respective accumulated gradients (aka momentum), " "followed by their respective accumulated squared gradients. For example, " "to optimize tensors \"X_1\" and \"X_2,\", the input list would be " "[\"X_1\", \"X_2\", " "gradient of \"X_1\", gradient of \"X_2\", " "accumulated gradient of \"X_1\", accumulated gradient of \"X_2\", " "accumulated squared gradient of \"X_1\", accumulated squared gradient of \"X_2\"].", "T3", OpSchema::Variadic, false) .Output( 0, "outputs", "New values of optimized tensors, " "followed by their respective new accumulated gradients, " "followed by their respective new accumulated squared gradients. " "For example, if two tensors \"X_1\" and \"X_2\" are optimized, " "the outputs list would be " "[new value of \"X_1\", new value of \"X_2\", " "new accumulated gradient of \"X_1\", " "new accumulated gradient of \"X_2\", " "new accumulated squared gradient of \"X_1\", " "new accumulated squared gradient of \"X_2\"].", "T3", OpSchema::Variadic, false) .Attr( "alpha", "Coefficient of previously accumulated gradient in running average. Default to 0.9.", AttributeProto::FLOAT, 0.9f) .Attr( "beta", "Coefficient of previously accumulated squared-gradient in running average. Default to 0.999.", AttributeProto::FLOAT, 0.999f) .Attr( "norm_coefficient", "Regularization coefficient of 0.5 * norm_coefficient * ||X||_2^2. Default to 0, " "which means no regularization.", AttributeProto::FLOAT, 0.0f) .Attr( "norm_coefficient_post", "Regularization coefficient of 0.5 * norm_coefficient * ||X||_2^2. Default to 0, " "which means no regularization.", AttributeProto::FLOAT, 0.0f) .Attr( "epsilon", "Small scalar to avoid dividing by zero.", AttributeProto::FLOAT, 1e-6f) .TypeConstraint( "T1", {"tensor(float)", "tensor(double)"}, "Constrain input types to float scalars.") .TypeConstraint( "T2", {"tensor(int64)"}, "Constrain input types to 64-bit integer scalars.") .TypeConstraint( "T3", {"tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext &ctx) { // Assume that the input list is [R, T, X1, X2, G1, G2, V1, V2, H1, H2] and // output list is [X1_new, X2_new, V1_new, V2_new, H1_new, H2_new] for explaining // the code below in a simpler way. // The count of input tensors excluding "R" and "T". auto num_adjustable_tensors = ctx.getNumInputs() - 2; // Check number of (optimized tensor, gradient, momentum) tuples. if (num_adjustable_tensors % 4 != 0) fail_shape_inference( "The sum of optimized tensor count, gradient tensor count, momentum tensor count, ", "accumulated squared-gradient tensor count should be a multiple of 4 in the ", "\"inputs\" of Adam operator."); // The count of "X1" and "X2". auto num_optimized_tensors = num_adjustable_tensors / 4; for (size_t i = 0; i < num_optimized_tensors; ++i){ // Pass X1's/X2's shapes to X1_new/X2_new. size_t i_in = 2 + i; size_t i_out = i; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); // Pass V1's/V2's shapes to V1_new/V2_new. i_in = 2 + 2 * num_optimized_tensors + i; i_out = num_optimized_tensors + i; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); // Pass H1's/H2's shapes to H1_new/H2_new. i_in = 2 + 3 * num_optimized_tensors + i; i_out = 2 * num_optimized_tensors + i; propagateElemTypeFromInputToOutput(ctx, i_in, i_out); propagateShapeFromInputToOutput(ctx, i_in, i_out); }})); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/attr_proto_util.cc0000664000000000000000000000570113655345213016657 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "attr_proto_util.h" namespace ONNX_NAMESPACE { #define ADD_BASIC_ATTR_IMPL(type, enumType, field) \ AttributeProto MakeAttribute( \ const std::string& attr_name, const type& value) { \ AttributeProto a; \ a.set_name(attr_name); \ a.set_type(enumType); \ a.set_##field(value); \ return a; \ } #define ADD_ATTR_IMPL(type, enumType, field) \ AttributeProto MakeAttribute( \ const std::string& attr_name, const type& value) { \ AttributeProto a; \ a.set_name(attr_name); \ a.set_type(enumType); \ *(a.mutable_##field()) = value; \ return a; \ } #define ADD_LIST_ATTR_IMPL(type, enumType, field) \ AttributeProto MakeAttribute( \ const std::string& attr_name, const std::vector& values) { \ AttributeProto a; \ a.set_name(attr_name); \ a.set_type(enumType); \ for (const auto& val : values) { \ *(a.mutable_##field()->Add()) = val; \ } \ return a; \ } ADD_BASIC_ATTR_IMPL(float, AttributeProto_AttributeType_FLOAT, f) ADD_BASIC_ATTR_IMPL(int64_t, AttributeProto_AttributeType_INT, i) ADD_BASIC_ATTR_IMPL(std::string, AttributeProto_AttributeType_STRING, s) ADD_ATTR_IMPL(TensorProto, AttributeProto_AttributeType_TENSOR, t) ADD_ATTR_IMPL(GraphProto, AttributeProto_AttributeType_GRAPH, g) ADD_LIST_ATTR_IMPL(float, AttributeProto_AttributeType_FLOATS, floats) ADD_LIST_ATTR_IMPL(int64_t, AttributeProto_AttributeType_INTS, ints) ADD_LIST_ATTR_IMPL(std::string, AttributeProto_AttributeType_STRINGS, strings) ADD_LIST_ATTR_IMPL(TensorProto, AttributeProto_AttributeType_TENSORS, tensors) ADD_LIST_ATTR_IMPL(GraphProto, AttributeProto_AttributeType_GRAPHS, graphs) AttributeProto MakeRefAttribute( const std::string& attr_name, AttributeProto_AttributeType type) { return MakeRefAttribute(attr_name, attr_name, type); } AttributeProto MakeRefAttribute( const std::string& attr_name, const std::string& referred_attr_name, AttributeProto_AttributeType type) { AttributeProto a; a.set_name(attr_name); a.set_ref_attr_name(referred_attr_name); a.set_type(type); return a; } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/schema.h0000664000000000000000000011024213655345213014524 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "data_type_utils.h" #include "onnx/common/common.h" #include "onnx/common/constants.h" #include "onnx/defs/shape_inference.h" #include "onnx/onnx-operators_pb.h" namespace ONNX_NAMESPACE { struct FunctionBodyBuildContext { virtual const AttributeProto* getAttribute(const std::string& name) const = 0; virtual bool hasInput(int i) const = 0; virtual bool hasOutput(int i) const = 0; virtual ~FunctionBodyBuildContext() {} }; struct FunctionBodyBuildContextImpl : public FunctionBodyBuildContext { FunctionBodyBuildContextImpl(NodeProto& node_proto) : node_proto_(node_proto) { for (auto& attr : *node_proto.mutable_attribute()) { attributesByName_[attr.name()] = &attr; } } const AttributeProto* getAttribute(const std::string& name) const { auto iter = attributesByName_.find(name); if (iter == attributesByName_.end()) { return nullptr; } else { return iter->second; } } bool hasInput(int i) const { if (i >= node_proto_.input_size()) return false; return node_proto_.input(i) != ""; } bool hasOutput(int i) const { if (i >= node_proto_.output_size()) return false; return node_proto_.output(i) != ""; } std::unordered_map attributesByName_; NodeProto node_proto_; }; using FunctionBodyQueryFunction = std::function; class OpSchema; using ContextDependentFunctionBodyBuilder = std::function< bool(const FunctionBodyBuildContext&, const OpSchema&, FunctionProto&)>; class SchemaError final : public std::runtime_error { public: using std::runtime_error::runtime_error; SchemaError(const std::string& message) : std::runtime_error(message) {} const char* what() const noexcept override { if (!expanded_message_.empty()) { return expanded_message_.c_str(); } return std::runtime_error::what(); } void AppendContext(const std::string& context) { expanded_message_ = ONNX_NAMESPACE::MakeString( std::runtime_error::what(), "\n\n==> Context: ", context); } private: std::string expanded_message_; }; #define fail_schema(...) \ throw ONNX_NAMESPACE::SchemaError(ONNX_NAMESPACE::MakeString(__VA_ARGS__)); using OperatorSetVersion = int; using DataTypeSet = std::unordered_set; // Type constraint map. Key is type string. Value is data type set and // description. using TypeConstraintMap = std::unordered_map>; /** * @brief A class to record the schema of an op. * * OpSchema records the common interface of an op specified by its name. * * To register an OpSchema, one can use the macro ONNX_OPERATOR_SCHEMA(name) and * then append the various functions in the class. For example, for an op * that takes in two inputs, one output, and the first input and output * could be in-place, can be written as * * ONNX_OPERATOR_SCHEMA(name) * .NumInputs(2).NumOutputs(1).AllowConsumed({{0, 0}}); * * To manufacture methods that may be used to register an OpSchema * non-statically, the following may be used: * * ONNX_OPERATOR_SET_SCHEMA(name, version, OpSchema() * .NumInputs(2).NumOutputs(1).AllowConsumed({{0, 0}})); */ class OpSchema final { public: // Formal parameter options. enum FormalParameterOption : uint8_t { // The formal parameter is single and not optional. // Number of supplied actual parameters must be 1. Single = 0, // The formal parameter is single and optional. // Number of supplied actual parameters may be 0 or 1. Optional = 1, // The formal parameter is variadic. // Number of supplied actual parameters must be N or more, where // the minimum value N is indicated separately (default value 1). Variadic = 2, }; // Formal parameter represenation, including input/output name, typeStr, // description, and type constraints. class FormalParameter final { public: // Constructor. FormalParameter() = default; explicit FormalParameter( std::string name, DataTypeSet allowed_type_set, std::string type_str, const std::string& description, FormalParameterOption param_option = Single, bool is_homogeneous = true, int min_arity = 1) : name_(std::move(name)), type_set_(std::move(allowed_type_set)), type_str_(std::move(type_str)), #ifndef __ONNX_NO_DOC_STRINGS description_(description), #endif param_option_(param_option), is_homogeneous_(is_homogeneous), min_arity_(min_arity) { } explicit FormalParameter( std::string name, const std::string& description, std::string type_str, FormalParameterOption param_option = Single, bool is_homogeneous = true, int min_arity = 1) : name_(std::move(name)), type_str_(std::move(type_str)), #ifndef __ONNX_NO_DOC_STRINGS description_(description), #endif param_option_(param_option), is_homogeneous_(is_homogeneous), min_arity_(min_arity) { } // Get formal parameter name. const std::string& GetName() const; // Get allowed data types. const DataTypeSet& GetTypes() const; // Get formal parameter type string. const std::string& GetTypeStr() const; // Get formal parameter description. const std::string& GetDescription() const; // Get the parameter option, it could be Single, Optional or Variadic. FormalParameterOption GetOption() const; // Get whether a variadic parameter requires all to be of same type bool GetIsHomogeneous() const; // Get minimum arity. Applicable only in the Variadic case. int GetMinArity() const; private: friend class OpSchema; DataTypeSet& MutableTypes(); // Formal parameter name. std::string name_; // A set of data types supported for <*this> formal parameter. // It should contain at least one element if this formal parameter is good. DataTypeSet type_set_; // The string specified when registring an op. // It could be a supported data type or a type constraint key, which // maps to a set of supported data types. std::string type_str_; // Formal parameter description. std::string description_; // Formal parameter option. FormalParameterOption param_option_; // For variadic parameters, a flag indicating if all parameters must be of // same type bool is_homogeneous_; // Minimum number of parameters expected. Applicable only for Variadic. int min_arity_; }; enum class SupportType : uint8_t { COMMON, // Supported by all frameworks that support this IR. EXPERIMENTAL, // This OP is experimental and can be changed or removed in // the future. }; OpSchema() : OpSchema("unknown", "unknown", 0) {} OpSchema(std::string name, std::string file, int line) : name_(std::move(name)), file_(std::move(file)), line_(line), support_(SupportType::COMMON) {} /** * @brief Returns the file that the op schema is registered from. */ const std::string& file() const { return file_; } /** * @brief Returns the line in file that the op schema is registered from. */ int line() const { return line_; } /** * @brief Returns the support level of the op schema. */ SupportType support_level() const { return support_; } /** * @brief Returns the docstring of the op schema. */ const char* doc() const { return doc_.empty() ? nullptr : doc_.c_str(); } // Check if input and output types fall into valid set and match each other void CheckInputOutputType(struct InferenceContext&) const; /** * @brief Verifies if a NodeProto matches the pattern specified in * the schema. */ void Verify(const NodeProto& node) const; // Functions to set the property of the operator schemas. // Sets the number of inputs, either a fixed number or a min and a max. /** * The earliest operator set version which this operator was * present in. If an operator has had no BC-breaking changes, * this is simply the first operator set the operator was a member * of; if it has had BC-breaking changes, then for the semantics * /as described/ in the OpSchema entry, this version describes * the operator set which introduced the BC-breaking change. * * For example, suppose op Foo was added in v3, and had a BC-breaking * change in v6. Then there will be an op schema entry for Foo with * SinceVersion(3), and another, updated op schema entry for Foo * with SinceVersion(6). */ OpSchema& SinceVersion(OperatorSetVersion n); // aka int /** * Marks this op as deprecated as of it's since_version. This will cause the * Schema() lookup functions to return nullptr when the version is in the * deprecated range. */ OpSchema& Deprecate(); bool Deprecated() const { return deprecated_; } /** * @brief Input could be one of the values specified in allowed_input_nums. */ OpSchema& NumInputs(std::set allowed_input_nums); /** * @brief Output could be one of the values specified in allowed_output_nums. */ OpSchema& NumOutputs(std::set allowed_output_nums); // Shape Inference // // Note that signatures are defined to allow for forward-declaring // any structs used from ir.h OpSchema& TypeAndShapeInferenceFunction(InferenceFunction inferenceFunction); InferenceFunction GetTypeAndShapeInferenceFunction() const { return tensor_inference_function_ ? tensor_inference_function_ : dummyInferenceFunction; } // Set the support level for the op schema. OpSchema& SetSupportLevel(SupportType supportType); // Functions to do documentation for the operator schema. // This may be disabled to save memory. OpSchema& SetDoc(const char* doc) { #ifndef __ONNX_NO_DOC_STRINGS SetDoc(std::string(doc)); #else ONNX_UNUSED_PARAMETER(doc); #endif return *this; } OpSchema& SetDoc(const std::string& doc) { #ifndef __ONNX_NO_DOC_STRINGS doc_ = doc; #else ONNX_UNUSED_PARAMETER(doc); #endif return *this; } // Functions to specify name for the operator schema. OpSchema& SetName(const char* name); OpSchema& SetName(std::string name); // Functions to specify code location for the operator schema. OpSchema& SetLocation(const char* file, int line); OpSchema& SetLocation(std::string file, int line); // Functions to specify domain for the operator schema. // Default domain value (ONNX_DOMAIN) means it's ONNX domain. OpSchema& SetDomain(const char* domain); OpSchema& SetDomain(std::string domain); struct Attribute final { Attribute( std::string name_, std::string description_, AttributeProto::AttributeType type_, bool required_) : name(std::move(name_)), description(std::move(description_)), type(type_), required(required_), default_value() {} Attribute( std::string name_, std::string description_, AttributeProto default_value_) : name(std::move(name_)), description(std::move(description_)), type(default_value_.type()), required(false), default_value(std::move(default_value_)) {} const std::string name; const std::string description; AttributeProto::AttributeType type; bool required; AttributeProto default_value; }; OpSchema& Attr(Attribute attr); // Register "optional" attribute with default value. #define ATTR_SETTER_WITH_DEFAULT_VALUE(TypeName) \ OpSchema& Attr( \ std::string name, \ std::string description, \ AttributeProto::AttributeType type, \ const TypeName& defaultValue); \ /* non-STL wrapper to reduce binary size */ \ OpSchema& Attr( \ const char* name, \ const char* description, \ AttributeProto::AttributeType type, \ const TypeName& defaultValue); \ OpSchema& Attr( \ std::string name, \ std::string description, \ AttributeProto::AttributeType type, \ const std::vector& defaultValue); ATTR_SETTER_WITH_DEFAULT_VALUE(int64_t) ATTR_SETTER_WITH_DEFAULT_VALUE(float) ATTR_SETTER_WITH_DEFAULT_VALUE(std::string) ATTR_SETTER_WITH_DEFAULT_VALUE(TensorProto) ATTR_SETTER_WITH_DEFAULT_VALUE(GraphProto) // Register "required" attribute without default value. OpSchema& Attr( std::string name, std::string description, AttributeProto::AttributeType type, bool required = true); // Non-STL wrapper to reduce binary size OpSchema& Attr( const char* name, const char* description, AttributeProto::AttributeType type, bool required = true); OpSchema& AllowUncheckedAttributes(); // Type constraint. struct TypeConstraintParam final { TypeConstraintParam( std::string type_param_str_, std::vector allowed_type_strs_, std::string description_) : type_param_str(std::move(type_param_str_)), allowed_type_strs(std::move(allowed_type_strs_)), description(std::move(description_)) {} // Type parameter string, for example, "T", "T1", etc. std::string type_param_str; // Allowed type strings for <*this> type parameter, for example, // "tensor(float)". std::vector allowed_type_strs; // Type parameter description. std::string description; }; // Grammar for type strings used in Input(), Output(). // ::= | // tensor() | // seq() | // map(, ) | // // :: = float | int32 | string | bool | uint8 // | int8 | uint16 | int16 | int64 | float16 | double // ::= any type parameter string, say "T". // // NOTE: 1) will always be together with a type constraints // specification. // 2) ::= means the data is scalar (zero dimension). // // Example: // ONNX_OPERATOR_SET_SCHEMA(Sum, 1, OpSchema() // .Input(0, "input_a", "the first input", "T") // .Input(1, "input_b", "the second input", "T") // .Output(0, "sum", "the sum of two numbers", "T") // .TypeConstraint("T", {"float", "double", "int32"}, "allowed data types for // sum.")) // // Optional = true means that the input might have empty input value // (represented as "") in the graph even though the later inputs have values. // It's useful for complex situation when there are several independent // optional inputs. OpSchema& Input( int n, std::string name, const std::string& description, std::string type_str, FormalParameterOption param_option = Single, bool is_homogeneous = true, int min_arity = 1); // Non-STL wrapper to reduce binary size OpSchema& Input( int n, const char* name, const char* description, const char* type_str, FormalParameterOption param_option = Single, bool is_homogeneous = true, int min_arity = 1); OpSchema& Output( int n, std::string name, const std::string& description, std::string type_str, FormalParameterOption param_option = Single, bool is_homogeneous = true, int min_arity = 1); // Non-STL wrapper to reduce binary size OpSchema& Output( int n, const char* name, const char* description, const char* type_str, FormalParameterOption param_option = Single, bool is_homogeneous = true, int min_arity = 1); OpSchema& TypeConstraint( std::string type_str, std::vector constraints, std::string description); // Non-STL wrapper to reduce binary size OpSchema& TypeConstraint( const char* type_str, std::initializer_list constraints, const char* description); // Convenience members for types // All high-precision numeric types. static const std::vector& numeric_types_for_math_reduction() { static const std::vector numeric_types_for_math_reduction = { "tensor(uint32)", "tensor(uint64)", "tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)"}; return numeric_types_for_math_reduction; } static const std::vector& all_numeric_types() { static const std::vector all_numeric_types = { "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)"}; return all_numeric_types; } static const std::vector& all_numeric_sequence_types() { static const std::vector all_numeric_sequence_types = { "seq(tensor(uint8))", "seq(tensor(uint16))", "seq(tensor(uint32))", "seq(tensor(uint64))", "seq(tensor(int8))", "seq(tensor(int16))", "seq(tensor(int32))", "seq(tensor(int64))", "seq(tensor(float16))", "seq(tensor(float))", "seq(tensor(double))"}; return all_numeric_sequence_types; } static const std::vector& all_tensor_types() { static const std::vector all_tensor_types = { "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)", "tensor(string)", "tensor(bool)", "tensor(complex64)", "tensor(complex128)"}; return all_tensor_types; } static const std::vector& all_tensor_sequence_types() { static const std::vector all_tensor_sequence_types = { "seq(tensor(uint8))", "seq(tensor(uint16))", "seq(tensor(uint32))", "seq(tensor(uint64))", "seq(tensor(int8))", "seq(tensor(int16))", "seq(tensor(int32))", "seq(tensor(int64))", "seq(tensor(float16))", "seq(tensor(float))", "seq(tensor(double))", "seq(tensor(string))", "seq(tensor(bool))", "seq(tensor(complex64))", "seq(tensor(complex128))"}; return all_tensor_sequence_types; } // Calls the passed function with `this` as an argument. Useful for // adding docs for temlated/macro ops. OpSchema& FillUsing(const std::function& populator); friend std::ostream& operator<<(std::ostream& out, const OpSchema& schema); const std::string& domain() const { return domain_; } const std::map& attributes() const { return attributes_; } // Get input formal parameters. const std::vector& inputs() const { return inputs_; } // Get output formal parameters. const std::vector& outputs() const { return outputs_; } const std::vector& typeConstraintParams() const { return type_constraint_params_; } const std::string& Name() const { return name_; } OperatorSetVersion SinceVersion() const { return since_version_; } int since_version() const { return since_version_; } bool deprecated() const { return deprecated_; } int min_input() const { return min_input_; } int max_input() const { return max_input_; } int min_output() const { return min_output_; } int max_output() const { return max_output_; } bool has_type_and_shape_inference_function() const { return tensor_inference_function_ ? true : false; } bool HasFunction() const { return function_body_.node_size() > 0; } OpSchema& FunctionBody(const std::vector& func_nodes); OpSchema& FunctionBody( const std::vector& func_nodes, const std::vector& opsets); const FunctionProto* GetFunction() const; bool HasContextDependentFunction() const { return functionBuilder_ != nullptr; } OpSchema& SetContextDependentFunctionBodyBuilder( ContextDependentFunctionBodyBuilder); bool BuildContextDependentFunction( const FunctionBodyBuildContext& ctx, FunctionProto& functionProto) const; // Verifies that the schema is valid and all specifications are compatible. // It will also parse all type strings specified for inputs/outputs into valid // TypeProto and create global unique string pointer as the DataType for // efficiency. void Finalize(); // Build function with information stored in opschema void BuildFunction(FunctionProto& function_body) const; private: void ParseAndSetTypes( /*out*/ std::vector* formalParameters); std::string name_; std::string file_; std::string doc_; // Default domain value ("") means it's ONNX domain. std::string domain_ = ONNX_DOMAIN; std::map attributes_{}; bool allows_unchecked_attributes_ = false; std::vector inputs_; std::vector outputs_; std::vector type_constraint_params_; TypeConstraintMap type_constraints_; int line_ = 0; SupportType support_; int min_input_ = 0; int max_input_ = 0; int min_output_ = 0; int max_output_ = 0; // The default is a little goofy, since it is never what you want OperatorSetVersion since_version_ = 1; bool deprecated_{}; std::function num_inputs_allowed_ = [](int) { return true; }; std::function num_outputs_allowed_ = [](int) { return true; }; InferenceFunction tensor_inference_function_; FunctionProto function_body_; ContextDependentFunctionBodyBuilder functionBuilder_; }; // Map type to store operator schemas. The format is, // >>. using OpName_Domain_Version_Schema_Map = std::unordered_map< std::string, std::unordered_map>>; class ISchemaRegistry { public: virtual ~ISchemaRegistry() = default; virtual const OpSchema* GetSchema( const std::string& key, const int maxInclusiveVersion, const std::string& domain = ONNX_DOMAIN) const = 0; }; /** * @brief A registry to hold all the operator schemas. */ class OpSchemaRegistry final : public ISchemaRegistry { public: // A singleton class to store domain to min/max op_set version map. class DomainToVersionRange final { public: DomainToVersionRange() { // Increase the highest version when you make BC-breaking changes to the // operator schema on specific domain. Update the lowest version when it's // determined to remove too old version history. map_[ONNX_DOMAIN] = std::make_pair(1, 12); map_[AI_ONNX_ML_DOMAIN] = std::make_pair(1, 2); map_[AI_ONNX_TRAINING_DOMAIN] = std::make_pair(1, 1); // ONNX's preview domain contains operators subject to change, so versining // is not meaningful and that domain should have only one version. map_[AI_ONNX_PREVIEW_TRAINING_DOMAIN] = std::make_pair(1, 1); } const std::unordered_map>& Map() const { return map_; } // Add customized domain to min/max version. // Onnx partners are able to use onnx operator schema api to // register customized op in their own domain. void AddDomainToVersion( const std::string& domain, int min_version, int max_version) { std::lock_guard lock(mutex_); assert(map_.end() == map_.find(domain)); map_[domain] = std::make_pair(min_version, max_version); } static DomainToVersionRange& Instance(); private: // Key: domain. Value: pair. std::unordered_map> map_; std::mutex mutex_; }; class OpSchemaRegisterOnce final { public: OpSchemaRegisterOnce(OpSchema& op_schema) { try { op_schema.Finalize(); auto& m = GetMapWithoutEnsuringRegistration(); auto& op_name = op_schema.Name(); auto& op_domain = op_schema.domain(); auto ver = op_schema.SinceVersion(); if (m[op_name][op_domain].count(ver)) { const auto& schema = m[op_name][op_domain][ver]; std::stringstream err; err << "Trying to register schema with name " << op_name << " (domain: " << op_domain << " version: " << ver << ") from file " << op_schema.file() << " line " << op_schema.line() << ", but it is already registered from file " << schema.file() << " line " << schema.line() << std::endl; fail_schema(err.str()); } auto ver_range_map = DomainToVersionRange::Instance().Map(); auto ver_range_it = ver_range_map.find(op_domain); if (ver_range_it == ver_range_map.end()) { std::stringstream err; err << "Trying to register schema with name " << op_name << " (domain: " << op_domain << " version: " << ver << ") from file " << op_schema.file() << " line " << op_schema.line() << ", but it its domain is not" << " known by the checker." << std::endl; fail_schema(err.str()); } auto lower_bound_incl = ver_range_it->second.first; auto upper_bound_incl = ver_range_it->second.second; if (!(lower_bound_incl <= ver && upper_bound_incl >= ver)) { std::stringstream err; err << "Trying to register schema with name " << op_name << " (domain: " << op_domain << " version: " << ver << ") from file " << op_schema.file() << " line " << op_schema.line() << ", but it its version is not " << "in the inclusive range [" << lower_bound_incl << ", " << upper_bound_incl << "] (usually, this means you " << "bumped the operator version but " << "forgot to update the version range in DomainToVersionRange " << "in onnx/defs/schema.h)." << std::endl; fail_schema(err.str()); } m[op_name][op_domain].insert( std::pair(ver, std::move(op_schema))); } catch (const std::exception& e) { std::cerr << "Schema error: " << e.what() << std::endl; } } }; // Return the latest schema for an operator in specified domain. // Domain with default value ONNX_DOMAIN means ONNX. static const OpSchema* Schema( const std::string& key, const std::string& domain = ONNX_DOMAIN) { auto& m = map(); if (m.count(key) && m[key].count(domain)) { return &m[key][domain].rbegin()->second; } else { return nullptr; } } // Return the schema with biggest version, which is not greater than specified // in specified domain. Domain with default value // ONNX_DOMAIN means ONNX. static const OpSchema* Schema( const std::string& key, const int maxInclusiveVersion, const std::string& domain = ONNX_DOMAIN) { auto& m = map(); if (m.count(key) && m[key].count(domain)) { auto pos = m[key][domain].lower_bound(maxInclusiveVersion); if (m[key][domain].begin() == pos && pos->first > maxInclusiveVersion) { // All versions are greater than specified version. return nullptr; } if (m[key][domain].end() == pos || pos->first > maxInclusiveVersion) { // All versions are less than specified version, or, // The version is greater than specified version. pos--; } // Schema with exact version as specified one exists. return &(pos->second); } else { return nullptr; } } static OpSchemaRegistry* Instance(); const OpSchema* GetSchema( const std::string& key, const int maxInclusiveVersion, const std::string& domain = ONNX_DOMAIN) const override { return Schema(key, maxInclusiveVersion, domain); } private: // OpSchemaRegistry should not need to be instantiated except statically // within this class OpSchemaRegistry() = default; /** * @brief Returns the underlying string to OpSchema map. * * You should not manually manipulate the map object returned. Instead, use * the macros defined such as ONNX_OPERATOR_SET_SCHEMA to register your * operator schema. * * We wrap it inside a function to avoid the static initialization order * fiasco. */ static OpName_Domain_Version_Schema_Map& GetMapWithoutEnsuringRegistration(); static OpName_Domain_Version_Schema_Map& map(); public: static const std::vector get_all_schemas_with_history() { std::vector r; for (auto& x : map()) { for (auto& y : x.second) { for (auto& z : y.second) { r.emplace_back(z.second); } } } return r; } static const std::vector get_all_schemas() { std::vector r; for (auto& x : map()) { for (auto& y : x.second) { auto& version2schema = y.second; r.emplace_back(version2schema.rbegin()->second); } } return r; } }; void RegisterSchema(OpSchema&& schema); // Registers all schema of a given operator set template void RegisterOpSetSchema() { T::ForEachSchema(RegisterSchema); }; // Forward declaration for the non-specialized GetOpSchema method. This // enforces a consistent signature on functions that query individual schema, // which are defined as specializations of this function. template OpSchema GetOpSchema(); #define ONNX_OPERATOR_SET_SCHEMA(name, ver, impl) \ ONNX_OPERATOR_SET_SCHEMA_EX(name, Onnx, ONNX_DOMAIN, ver, true, impl) #define ONNX_ML_OPERATOR_SET_SCHEMA(name, ver, impl) \ ONNX_OPERATOR_SET_SCHEMA_EX(name, OnnxML, AI_ONNX_ML_DOMAIN, ver, true, impl) #define ONNX_TRAINING_OPERATOR_SET_SCHEMA(name, ver, impl) \ ONNX_OPERATOR_SET_SCHEMA_EX( \ name, OnnxTraining, AI_ONNX_TRAINING_DOMAIN, ver, true, impl) #define ONNX_PREVIEW_TRAINING_OPERATOR_SET_SCHEMA(name, ver, impl) \ ONNX_OPERATOR_SET_SCHEMA_EX( \ name, OnnxPreview, AI_ONNX_PREVIEW_TRAINING_DOMAIN, ver, true, impl) // Defines specialization of GetOpSchema for a class whose name is determined // based on a convention using name, domain, and version. Operator schema are // normally included in operator sets and registered in OpSchemaRegistry::map(). // In this case, callers should set dbg_included_in_static_opset to true. This // assists with runtime validation in in DEBUG builds ensuring the intended set // of operator schema is registered. #define ONNX_OPERATOR_SET_SCHEMA_EX( \ name, domain, domain_str, ver, dbg_included_in_static_opset, impl) \ class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(domain, ver, name); \ template <> \ OpSchema \ GetOpSchema() { \ return impl.SetName(#name) \ .SetDomain(domain_str) \ .SinceVersion(ver) \ .SetLocation(__FILE__, __LINE__); \ } \ size_t dbg_count_check_##name##_##domain##_ver##ver = \ (dbg_included_in_static_opset) ? ONNX_DBG_INCREMENT_COUNT_IN_OPSETS() \ : 0; #ifdef NDEBUG #define ONNX_DBG_INCREMENT_COUNT_IN_OPSETS() 0 #else #define ONNX_DBG_INCREMENT_COUNT_IN_OPSETS() \ DbgOperatorSetTracker::Instance().IncrementCount() #define ONNX_DBG_GET_COUNT_IN_OPSETS() \ DbgOperatorSetTracker::Instance().GetCount() class DbgOperatorSetTracker { public: static DbgOperatorSetTracker& Instance(); size_t IncrementCount() { return ++count_; } size_t GetCount() const { return count_; } private: size_t count_ = 0; }; #endif // Naming convention for operator schema classes #define ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(domain, ver, name) \ name##_##domain##_ver##ver // Naming convention for preview operator schema classes #define ONNX_PREVIEW_OPERATOR_SET_SCHEMA_CLASS_NAME(ver, name) \ ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(OnnxPreview, ver, name) // Helper function size_t ReplaceAll(std::string& s, const char* from, const char* to); #ifdef __GNUC__ #define ONNX_UNUSED __attribute__((__unused__)) #else #define ONNX_UNUSED #endif // Legacy macros to register schema at static initialization #define ONNX_OPERATOR_SCHEMA(name) \ ONNX_OPERATOR_SCHEMA_UNIQ_HELPER(__COUNTER__, name) #define ONNX_OPERATOR_SCHEMA_UNIQ_HELPER(Counter, name) \ ONNX_OPERATOR_SCHEMA_UNIQ(Counter, name) #define ONNX_OPERATOR_SCHEMA_UNIQ(Counter, name) \ static ONNX_NAMESPACE::OpSchemaRegistry::OpSchemaRegisterOnce( \ op_schema_register_once##name##Counter) ONNX_UNUSED = \ OpSchema(#name, __FILE__, __LINE__) // Helper function size_t ReplaceAll(std::string& s, const char* from, const char* to); inline std::string GenerateOptionalArgumentsDoc() { return "This operator has **optional** inputs/outputs. " "See [the doc](IR.md) for more details about the representation of " "optional arguments. An empty string may be used in the place of " "an actual argument's name to indicate a missing argument. " "Trailing optional arguments (those not followed by an argument " "that is present) may also be simply omitted.\n"; } inline std::string GenerateBroadcastingDocMul() { return "This operator supports **multidirectional (i.e., Numpy-style) broadcasting**;" " for more details please check [the doc](Broadcasting.md)."; } inline std::string GenerateBroadcastingDocUni( const char* from, const char* to) { std::string ret = "This operator supports **unidirectional broadcasting** ("; ret = ret + from + " should be unidirectional broadcastable to " + to + ");" " for more details please check [the doc](Broadcasting.md)."; return ret; } /* * Macros for setting operator documentation * Use this macro for simple SetDoc() calls that generate documentation * directly. This is the macro to use in almost all cases. * Sample usage guidelines: * const char* doc_str = "foo"; * SetDoc(GET_OP_DOC_STR(doc_str)) * * SetDoc(GET_OP_DOC_STR( std::string(BitShift_ver11_doc) + GenerateBroadcastingDocMul())) */ #ifndef __ONNX_NO_DOC_STRINGS #define GET_OP_DOC_STR(doc_str) (doc_str) #else #define GET_OP_DOC_STR(doc_str) ("") #endif /* * Use this macro when the documentation needs to be populated in some * complicated way like string substitutions, etc before calling SetDoc. * Sample usage guidelines: std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( Returns the tensor resulted from performing the `{name}` logical operation elementwise on the input tensors `A` and `B` (with Numpy-style broadcasting support). {broadcast_doc} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll( doc, "{broadcast_doc}", GenerateBroadcastingDocMul().c_str());); schema.SetDoc(doc); * */ #ifndef __ONNX_NO_DOC_STRINGS #define POPULATE_OP_DOC_STR(DocPopulatorCode) \ do { \ DocPopulatorCode \ } while (0) #else #define POPULATE_OP_DOC_STR(DocPopulatorCode) #endif } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/tensor_proto_util.h0000664000000000000000000000063513655345213017062 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/onnx-operators_pb.h" namespace ONNX_NAMESPACE { template TensorProto ToTensor(const T& value); template TensorProto ToTensor(const std::vector& values); template const std::vector ParseData(const TensorProto* tensor_proto); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/__init__.py0000664000000000000000000000256113655345213015230 0ustar rootrootfrom __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from onnx import AttributeProto, FunctionProto import onnx.onnx_cpp2py_export.defs as C from collections import defaultdict from typing import List, Dict ONNX_DOMAIN = "" ONNX_ML_DOMAIN = 'ai.onnx.ml' AI_ONNX_PREVIEW_TRAINING_DOMAIN = 'ai.onnx.preview.training' has = C.has_schema get_schema = C.get_schema get_all_schemas = C.get_all_schemas get_all_schemas_with_history = C.get_all_schemas_with_history def onnx_opset_version(): # type: () -> int return C.schema_version_map()[ONNX_DOMAIN][1] @property # type: ignore def _Function_proto(self): # type: ignore func_proto = FunctionProto() func_proto.ParseFromString(self._function_body) return func_proto OpSchema = C.OpSchema # type: ignore C.OpSchema.function_body = _Function_proto # type: ignore @property # type: ignore def _Attribute_default_value(self): # type: ignore attr = AttributeProto() attr.ParseFromString(self._default_value) return attr OpSchema.Attribute.default_value = _Attribute_default_value # type: ignore def get_function_ops(): # type: () -> List[OpSchema] schemas = C.get_all_schemas() return [schema for schema in schemas if schema.has_function or schema.has_context_dependent_function] # type: ignore onnx-1.7.0/onnx/defs/gen_doc.py0000664000000000000000000003407013655345213015067 0ustar rootroot#!/usr/bin/env python from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals from collections import defaultdict import io import os import sys import numpy as np # type: ignore from onnx import defs, FunctionProto, helper, OperatorStatus from onnx.defs import OpSchema, ONNX_DOMAIN, ONNX_ML_DOMAIN from onnx.backend.test.case import collect_snippets from onnx.backend.sample.ops import collect_sample_implementations from typing import Any, Text, Sequence, Dict, List, Type, Set, Tuple SNIPPETS = collect_snippets() SAMPLE_IMPLEMENTATIONS = collect_sample_implementations() ONNX_ML = not bool(os.getenv('ONNX_ML') == '0') ext = '-ml.md' if ONNX_ML else '.md' def display_number(v): # type: (int) -> Text if defs.OpSchema.is_infinite(v): return '∞' return Text(v) def should_render_domain(domain): # type: (Text) -> bool if domain == ONNX_ML_DOMAIN and not ONNX_ML: return False if ONNX_ML and domain != ONNX_ML_DOMAIN: return False return True def format_name_with_domain(domain, schema_name): # type: (Text, Text) -> Text if domain: return '{}.{}'.format(domain, schema_name) return schema_name def display_attr_type(v): # type: (OpSchema.AttrType) -> Text assert isinstance(v, OpSchema.AttrType) s = Text(v) s = s[s.rfind('.') + 1:].lower() if s[-1] == 's': s = 'list of ' + s return s def display_domain(domain): # type: (Text) -> Text if domain: return "the '{}' operator set".format(domain) return "the default ONNX operator set" def display_domain_short(domain): # type: (Text) -> Text if domain: return domain return 'ai.onnx (default)' def display_version_link(name, version): # type: (Text, int) -> Text changelog_md = 'Changelog' + ext name_with_ver = '{}-{}'.format(name, version) return '{}'.format(changelog_md, name_with_ver, name_with_ver) def display_schema(schema, versions): # type: (OpSchema, Sequence[OpSchema]) -> Text s = '' # doc if schema.doc: s += '\n' s += '\n'.join(' ' + line for line in schema.doc.lstrip().splitlines()) s += '\n' # since version s += '\n#### Version\n' if schema.support_level == OpSchema.SupportType.EXPERIMENTAL: s += '\nNo versioning maintained for experimental ops.' else: s += '\nThis version of the operator has been ' + ('deprecated' if schema.deprecated else 'available') + ' since version {}'.format(schema.since_version) s += ' of {}.\n'.format(display_domain(schema.domain)) if len(versions) > 1: # TODO: link to the Changelog.md s += '\nOther versions of this operator: {}\n'.format( ', '.join(display_version_link(format_name_with_domain(v.domain, v.name), v.since_version) for v in versions[:-1])) # If this schema is deprecated, don't display any of the following sections if schema.deprecated: return s # attributes if schema.attributes: s += '\n#### Attributes\n\n' s += '
\n' for _, attr in sorted(schema.attributes.items()): # option holds either required or default value opt = '' if attr.required: opt = 'required' elif attr.default_value.name: default_value = helper.get_attribute_value(attr.default_value) def format_value(value): # type: (Any) -> Text if isinstance(value, float): formatted = str(np.round(value, 5)) # use default formatting, unless too long. if (len(formatted) > 10): formatted = str("({:e})".format(value)) return formatted elif isinstance(value, (bytes, bytearray)) and sys.version_info[0] == 3: return str(value.decode('utf-8')) return str(value) if isinstance(default_value, list): default_value = [format_value(val) for val in default_value] else: default_value = format_value(default_value) opt = 'default is {}'.format(default_value) s += '
{} : {}{}
\n'.format( attr.name, display_attr_type(attr.type), ' ({})'.format(opt) if opt else '') s += '
{}
\n'.format(attr.description) s += '
\n' # inputs s += '\n#### Inputs' if schema.min_input != schema.max_input: s += ' ({} - {})'.format(display_number(schema.min_input), display_number(schema.max_input)) s += '\n\n' if schema.inputs: s += '
\n' for input in schema.inputs: option_str = "" if OpSchema.FormalParameterOption.Optional == input.option: option_str = " (optional)" elif OpSchema.FormalParameterOption.Variadic == input.option: if input.isHomogeneous: option_str = " (variadic)" else: option_str = " (variadic, heterogeneous)" s += '
{}{} : {}
\n'.format(input.name, option_str, input.typeStr) s += '
{}
\n'.format(input.description) s += '
\n' # outputs s += '\n#### Outputs' if schema.min_output != schema.max_output: s += ' ({} - {})'.format(display_number(schema.min_output), display_number(schema.max_output)) s += '\n\n' if schema.outputs: s += '
\n' for output in schema.outputs: option_str = "" if OpSchema.FormalParameterOption.Optional == output.option: option_str = " (optional)" elif OpSchema.FormalParameterOption.Variadic == output.option: if output.isHomogeneous: option_str = " (variadic)" else: option_str = " (variadic, heterogeneous)" s += '
{}{} : {}
\n'.format(output.name, option_str, output.typeStr) s += '
{}
\n'.format(output.description) s += '
\n' # type constraints s += '\n#### Type Constraints' s += '\n\n' if schema.type_constraints: s += '
\n' for type_constraint in schema.type_constraints: allowedTypes = type_constraint.allowed_type_strs if (len(allowedTypes) > 0): allowedTypeStr = allowedTypes[0] for allowedType in allowedTypes[1:]: allowedTypeStr += ', ' + allowedType s += '
{} : {}
\n'.format( type_constraint.type_param_str, allowedTypeStr) s += '
{}
\n'.format(type_constraint.description) s += '
\n' # Function Body # TODO: this should be refactored to show the function body graph's picture (DAG). #if schema.has_function or schema.has_context_dependent_function: # type: ignore # s += '\n#### Function\n' # s += '\nThe Function can be represented as a function.\n' return s def support_level_str(level): # type: (OpSchema.SupportType) -> Text return \ "experimental " if level == OpSchema.SupportType.EXPERIMENTAL else "" def main(args): # type: (Type[Args]) -> None with io.open(args.changelog, 'w', newline='') as fout: fout.write('## Operator Changelog\n') fout.write( "*This file is automatically generated from the\n" " [def files](/onnx/defs) via [this script](/onnx/defs/gen_doc.py).\n" " Do not modify directly and instead edit operator definitions.*\n") # domain -> version -> [schema] dv_index = defaultdict(lambda: defaultdict(list)) # type: Dict[Text, Dict[int, List[OpSchema]]] for schema in defs.get_all_schemas_with_history(): dv_index[schema.domain][schema.since_version].append(schema) fout.write('\n') for domain, versionmap in sorted(dv_index.items()): if not should_render_domain(domain): continue s = '# {}\n'.format(display_domain_short(domain)) for version, unsorted_schemas in sorted(versionmap.items()): s += '## Version {} of {}\n'.format(version, display_domain(domain)) for schema in sorted(unsorted_schemas, key=lambda s: s.name): name_with_ver = '{}-{}'.format(format_name_with_domain(domain, schema.name), schema.since_version) s += ('### **{}**' + (' (deprecated)' if schema.deprecated else '') + '\n').format(name_with_ver, name_with_ver) s += display_schema(schema, [schema]) s += '\n' fout.write(s) with io.open(args.output, 'w', newline='', encoding="utf-8") as fout: fout.write('## Operator Schemas\n') fout.write( "*This file is automatically generated from the\n" " [def files](/onnx/defs) via [this script](/onnx/defs/gen_doc.py).\n" " Do not modify directly and instead edit operator definitions.*\n") # domain -> support level -> name -> [schema] index = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) # type: Dict[Text, Dict[int, Dict[Text, List[OpSchema]]]] for schema in defs.get_all_schemas_with_history(): index[schema.domain][int(schema.support_level)][schema.name].append(schema) fout.write('\n') # Preprocess the Operator Schemas # [(domain, [(support_level, [(schema name, current schema, all versions schemas)])])] operator_schemas = list() # type: List[Tuple[Text, List[Tuple[int, List[Tuple[Text, OpSchema, List[OpSchema]]]]]]] exsting_ops = set() # type: Set[Text] for domain, _supportmap in sorted(index.items()): if not should_render_domain(domain): continue processed_supportmap = list() for _support, _namemap in sorted(_supportmap.items()): processed_namemap = list() for n, unsorted_versions in sorted(_namemap.items()): versions = sorted(unsorted_versions, key=lambda s: s.since_version) schema = versions[-1] if schema.name in exsting_ops: continue exsting_ops.add(schema.name) processed_namemap.append((n, schema, versions)) processed_supportmap.append((_support, processed_namemap)) operator_schemas.append((domain, processed_supportmap)) # Table of contents for domain, supportmap in operator_schemas: s = '* {}\n'.format(display_domain_short(domain)) fout.write(s) function_ops = list() for _, namemap in supportmap: for n, schema, versions in namemap: if schema.has_function or schema.has_context_dependent_function: # type: ignore function_ops.append((n, schema, versions)) continue s = ' * {}{}\n'.format( support_level_str(schema.support_level), format_name_with_domain(domain, n), format_name_with_domain(domain, n)) fout.write(s) if len(function_ops): fout.write('\n') fout.write(' **Functions**\n') for n, schema, versions in function_ops: s = ' * {}{}\n'.format( support_level_str(schema.support_level), format_name_with_domain(domain, n), format_name_with_domain(domain, n)) fout.write(s) fout.write('\n') for domain, supportmap in operator_schemas: s = '## {}\n'.format(display_domain_short(domain)) fout.write(s) for _, namemap in supportmap: for op_type, schema, versions in namemap: # op_type s = ('### {}**{}**' + (' (deprecated)' if schema.deprecated else '') + '\n').format( support_level_str(schema.support_level), format_name_with_domain(domain, op_type), format_name_with_domain(domain, op_type.lower()), format_name_with_domain(domain, op_type)) s += display_schema(schema, versions) s += '\n\n' if op_type in SNIPPETS: s += '#### Examples\n\n' for summary, code in sorted(SNIPPETS[op_type]): s += '
\n' s += '{}\n\n'.format(summary) s += '```python\n{}\n```\n\n'.format(code) s += '
\n' s += '\n\n' if op_type.lower() in SAMPLE_IMPLEMENTATIONS: s += '#### Sample Implementation\n\n' s += '
\n' s += '{}\n\n'.format(op_type) s += '```python\n{}\n```\n\n'.format(SAMPLE_IMPLEMENTATIONS[op_type.lower()]) s += '
\n' s += '\n\n' fout.write(s) if __name__ == '__main__': base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) docs_dir = os.path.join(base_dir, 'docs') class Args(object): output = os.path.join(docs_dir, 'Operators' + ext) changelog = os.path.join(docs_dir, 'Changelog' + ext) main(Args) onnx-1.7.0/onnx/defs/shape_inference.h0000664000000000000000000006402213655345213016406 0ustar rootroot#pragma once #include "onnx/defs/data_type_utils.h" #include "onnx/proto_utils.h" #include "onnx/string_utils.h" namespace ONNX_NAMESPACE { using Dim = TensorShapeProto_Dimension; class GraphInferencer { public: // Perform inferencing on the graph contained in GraphInferencer. // Returns the graph output types post-inferencing. virtual std::vector doInferencing( const std::vector& inputTypes, const std::vector& inputData) = 0; virtual ~GraphInferencer() = default; }; // Exception class used for handling errors in type and shape inference class InferenceError final : public std::runtime_error { public: using std::runtime_error::runtime_error; InferenceError(const std::string& message) : std::runtime_error(message) {} const char* what() const noexcept override { if (!expanded_message_.empty()) { return expanded_message_.c_str(); } return std::runtime_error::what(); } void AppendContext(const std::string& context) { expanded_message_ = ONNX_NAMESPACE::MakeString( std::runtime_error::what(), "\n\n==> Context: ", context); } private: std::string expanded_message_; }; #define fail_type_inference(...) \ throw ONNX_NAMESPACE::InferenceError( \ ONNX_NAMESPACE::MakeString("[TypeInferenceError] ", __VA_ARGS__)); #define fail_shape_inference(...) \ throw ONNX_NAMESPACE::InferenceError( \ ONNX_NAMESPACE::MakeString("[ShapeInferenceError] ", __VA_ARGS__)); struct InferenceContext { virtual const AttributeProto* getAttribute(const std::string& name) const = 0; virtual size_t getNumInputs() const = 0; virtual const TypeProto* getInputType(size_t index) const = 0; virtual const TensorProto* getInputData(size_t index) const = 0; virtual size_t getNumOutputs() const = 0; virtual TypeProto* getOutputType(size_t index) = 0; virtual GraphInferencer* getGraphAttributeInferencer( const std::string& attribute_name) = 0; virtual ~InferenceContext() {} }; using InferenceFunction = std::function; // This no-op inference function is used for operators without an // inference implementation. inline void dummyInferenceFunction(InferenceContext&){}; template inline bool getRepeatedAttribute( InferenceContext& ctx, std::string attr_name, std::vector& values) { const auto* attr = ctx.getAttribute(attr_name); if (attr) { values = RetrieveValues(*attr); return true; } else { return false; } } inline int64_t getAttribute( InferenceContext& ctx, const std::string& attributeName, int64_t defaultValue) { auto attr_proto = ctx.getAttribute(attributeName); if ((nullptr != attr_proto) && attr_proto->has_i()) return attr_proto->i(); return defaultValue; } inline std::string getAttribute( InferenceContext& ctx, const std::string& attributeName, const std::string& defaultValue) { auto attr_proto = ctx.getAttribute(attributeName); if ((nullptr != attr_proto) && attr_proto->has_s()) return attr_proto->s(); return defaultValue; } inline TensorShapeProto::Dimension operator*( TensorShapeProto::Dimension dim1, TensorShapeProto::Dimension dim2) { TensorShapeProto::Dimension result; if (dim1.has_dim_value() && dim2.has_dim_value()) { result.set_dim_value(dim1.dim_value() * dim2.dim_value()); } else if (dim1.has_dim_value() && (dim1.dim_value() == 1)) { return dim2; } else if (dim2.has_dim_value() && (dim2.dim_value() == 1)) { return dim1; } return result; } inline TensorShapeProto::Dimension operator*( TensorShapeProto::Dimension dim1, int64_t dim2) { TensorShapeProto::Dimension result; if (dim1.has_dim_value()) { result.set_dim_value(dim1.dim_value() * dim2); } else if (dim2 == 1) { return dim1; } return result; } inline TensorShapeProto::Dimension operator/( TensorShapeProto::Dimension dim1, int64_t dim2) { TensorShapeProto::Dimension result; if (dim1.has_dim_value()) { result.set_dim_value(dim1.dim_value() / dim2); } else if (dim2 == 1) { return dim1; } return result; } // if from >= upto_exclusive, return 1. // Caller must make sure upto_exclusive is less than or equal to shape.size() // Caller must make sure from>=0 inline TensorShapeProto::Dimension multiplyDims(const TensorShapeProto& shape, int from, int upto_exclusive) { TensorShapeProto::Dimension dim; dim.set_dim_value(1); for (int i = from; i < upto_exclusive; ++i) { dim = dim * shape.dim(i); } return dim; } // propagate the element type from an input type to an output type. // if an existing output element type exists, validate it matches. inline void propagateElemTypeWithValidation( const TypeProto* input_type, TypeProto* output_type) { if (nullptr == input_type) { fail_type_inference("Input type was null"); } if (input_type->value_case() != TypeProto::kTensorType) { fail_type_inference( "Input was expected to have tensor type. Got ", input_type->value_case()); } if (input_type->tensor_type().elem_type() == TensorProto::UNDEFINED) { fail_type_inference("Element type of input was unknown"); } if (output_type->value_case() == TypeProto::VALUE_NOT_SET) { output_type->mutable_tensor_type()->set_elem_type( input_type->tensor_type().elem_type()); } else if (output_type->value_case() == TypeProto::kTensorType) { if (output_type->tensor_type().has_elem_type()) { if (input_type->tensor_type().elem_type() != output_type->tensor_type().elem_type()) { fail_type_inference( "Input element type of ", input_type->tensor_type().elem_type(), " does not match existing output type of ", output_type->tensor_type().elem_type()); } } else { output_type->mutable_tensor_type()->set_elem_type( input_type->tensor_type().elem_type()); } } else { // This is not expected to happen fail_type_inference( "Output was expected to have tensor type. Got ", output_type->value_case()); } } // Note: for all methods below for propagating type or shape, callers are // responsible to handle optional inputs/outputs and ensure that the specified // index value is less than NumInputs/NumOutputs. inline void propagateElemTypeFromInputToOutput( InferenceContext& ctx, size_t inputIndex, size_t outputIndex) { auto input_type = ctx.getInputType(inputIndex); if (nullptr == input_type || input_type->value_case() != TypeProto::kTensorType) { fail_type_inference("Input ", inputIndex, " expected to have tensor type"); } if (input_type->tensor_type().elem_type() == TensorProto::UNDEFINED) { fail_type_inference("Element type of input ", inputIndex, " unknown"); } auto output_type = ctx.getOutputType(outputIndex); if (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET) { output_type->mutable_tensor_type()->set_elem_type( input_type->tensor_type().elem_type()); } else { // This is not expected to happen fail_type_inference( "Output ", outputIndex, " expected to have tensor type"); } } inline void propagateElemTypeFromDtypeToOutput( InferenceContext& ctx, const int& data_type, size_t outputIndex) { auto attribute_tensor_datatype = data_type; auto output_type = ctx.getOutputType(outputIndex); if (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET) { output_type->mutable_tensor_type()->set_elem_type( attribute_tensor_datatype); } else { // This is not expected to happen fail_type_inference( "Output ", outputIndex, " expected to have tensor type"); } } inline void propagateElemTypeFromDtypeToOutput( InferenceContext& ctx, const AttributeProto* attr, size_t outputIndex) { if (attr->type() != AttributeProto::TENSOR) { fail_type_inference("Attribute expected to have tensor type"); } if (attr->t().dims().size() != 1) { fail_type_inference("Attribute expected to have a one-dim tensor"); } auto attribute_tensor_datatype = attr->t().data_type(); propagateElemTypeFromDtypeToOutput( ctx, attribute_tensor_datatype, outputIndex); } inline bool hasShape(const TypeProto& type) { if (type.has_tensor_type()) { return type.tensor_type().has_shape(); } else if (type.has_sequence_type() && type.sequence_type().has_elem_type()) { return hasShape(type.sequence_type().elem_type()); } return false; } inline bool hasInputShape(InferenceContext& ctx, size_t n) { return ctx.getNumInputs() > static_cast(n) && ctx.getInputType(n) && hasShape(*ctx.getInputType(n)); } inline bool hasNInputShapes(InferenceContext& ctx, size_t n) { for (size_t i = 0; i < n; i++) { if (!hasInputShape(ctx, i)) { return false; } } return true; } inline const TensorShapeProto& getInputShape(InferenceContext& ctx, size_t n) { return ctx.getInputType(n)->tensor_type().shape(); } // Caller must make sure fromDimIndex is strictly less than shape.dim_size() inline void appendSingleDimCopiedFromInputTypeToOutputType( InferenceContext& ctx, size_t inputIndex, size_t outputIndex, size_t fromDimIndex) { auto output_type = ctx.getOutputType(outputIndex); auto input_type = ctx.getInputType(inputIndex); if (TypeProto::kTensorType != output_type->value_case()) { fail_type_inference( "Output ", outputIndex, " expected to have tensor type"); } if (TypeProto::kTensorType != input_type->value_case()) { fail_type_inference("Input ", inputIndex, " expected to have tensor type"); } auto* dim = ctx.getOutputType(outputIndex) ->mutable_tensor_type() ->mutable_shape() ->add_dim(); *dim = input_type->tensor_type().shape().dim(static_cast(fromDimIndex)); } inline void propagateShapeFromInputToOutput( InferenceContext& ctx, size_t inputIndex, size_t outputIndex) { auto output_type = ctx.getOutputType(outputIndex); auto input_type = ctx.getInputType(inputIndex); if (TypeProto::kTensorType != input_type->value_case() || TypeProto::kTensorType != output_type->value_case()) { throw std::runtime_error(ONNX_NAMESPACE::to_string( ctx.getInputType(inputIndex)->tensor_type().shape().dim_size())); } *ctx.getOutputType(outputIndex)->mutable_tensor_type()->mutable_shape() = ctx.getInputType(inputIndex)->tensor_type().shape(); } inline void propagateShapeAndTypeFromFirstInput(InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } propagateShapeFromInputToOutput(ctx, 0, 0); } inline void updateOutputElemType( InferenceContext& ctx, size_t outputIndex, int32_t elemType) { auto output_type = ctx.getOutputType(outputIndex); if ((output_type != nullptr) && (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET)) { output_type->mutable_tensor_type()->set_elem_type(elemType); } else { // This is not expected to happen fail_type_inference( "Output ", outputIndex, " expected to have tensor type"); } } // Infer type of an output from the value of a specified attribute, which is // expected to have a valid value representing a TensorProto_DataType. inline void propagateElemTypeFromAttributeToOutput( InferenceContext& ctx, const std::string& attributeName, size_t outputIndex, TensorProto_DataType default_value = TensorProto::UNDEFINED) { auto attr_proto = ctx.getAttribute(attributeName); if (nullptr == attr_proto) { // attribute not present if (default_value != TensorProto::UNDEFINED) { updateOutputElemType(ctx, outputIndex, default_value); return; } else fail_type_inference( "Value of attribute ", attributeName, " not specified"); } if (!attr_proto->has_i()) { fail_type_inference( "Attribute ", attributeName, " should be of integer type and specify a type."); } auto attr_value = attr_proto->i(); auto elem_type = static_cast(attr_value); if (!TensorProto_DataType_IsValid(elem_type)) { fail_type_inference( "Attribute ", attributeName, " does not specify a valid type."); } updateOutputElemType(ctx, outputIndex, elem_type); } inline TensorShapeProto* getOutputShape(InferenceContext& ctx, size_t n) { auto output_type = ctx.getOutputType(n); if ((output_type != nullptr) && (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET)) { return output_type->mutable_tensor_type()->mutable_shape(); } else fail_type_inference("Output ", n, " expected to have tensor type"); } inline void appendDim(TensorShapeProto* shape, int64_t dim_value) { shape->add_dim()->set_dim_value(dim_value); } inline void updateOutputShape( InferenceContext& ctx, size_t outputIndex, const TensorShapeProto& shape) { auto* output_shape = getOutputShape(ctx, outputIndex); *output_shape = shape; } inline void updateOutputShape( InferenceContext& ctx, size_t outputIndex, const TensorProto& tensorProto) { auto* output_shape = getOutputShape(ctx, outputIndex); for (auto d : tensorProto.dims()) { auto* dim = output_shape->add_dim(); dim->set_dim_value(d); } } inline void updateOutputShape( InferenceContext& ctx, size_t outputIndex, std::initializer_list dims) { auto* output_shape = getOutputShape(ctx, outputIndex); for (auto& d : dims) { auto* dim = output_shape->add_dim(); *dim = d; } } // Infer shape of an output from the value of a specified attribute, which is // expected to be a list of integers specifying a valid shape. inline void propagateShapeFromAttributeToOutput( InferenceContext& ctx, const std::string& attributeName, size_t outputIndex) { auto attr_proto = ctx.getAttribute(attributeName); if ((nullptr == attr_proto) || (!attr_proto->has_type()) || (attr_proto->type() != AttributeProto_AttributeType_INTS)) { fail_shape_inference( "Attribute ", attributeName, " should specify a shape"); } auto& int_list = attr_proto->ints(); TensorShapeProto shape; for (auto dim_size : int_list) { if (dim_size < 0) { fail_shape_inference( "Negative values are not allowed in a shape specification"); } shape.add_dim()->set_dim_value(dim_size); } updateOutputShape(ctx, outputIndex, shape); } inline void multidirectionalBroadcastShapeInference( const std::vector& shapes, TensorShapeProto& resultShape) { int result_shape_size = 0; // Get the result shape size. for (size_t i = 0; i < shapes.size(); ++i) { if (shapes[i]->dim_size() > result_shape_size) { result_shape_size = shapes[i]->dim_size(); } } for (int i = 0; i < result_shape_size; ++i) { int64_t dim_value = 1; TensorShapeProto_Dimension symbolic_dim; int num_symbolic_dims = 0; for (size_t j = 0; j < shapes.size(); ++j) { if (i < result_shape_size - shapes[j]->dim_size()) { // Shape j will be filled with 1 at dimension i; continue; } auto dim_i_j = shapes[j]->dim(i - result_shape_size + shapes[j]->dim_size()); if (dim_i_j.has_dim_value()) { if (dim_i_j.dim_value() != 1) { if (dim_value != dim_i_j.dim_value() && dim_value != 1) { fail_shape_inference("Incompatible dimensions"); } else { dim_value = dim_i_j.dim_value(); } } } else { if (num_symbolic_dims == 0) { symbolic_dim = dim_i_j; ++num_symbolic_dims; } else if (dim_i_j.dim_param() != symbolic_dim.dim_param()) { ++num_symbolic_dims; } } } if (dim_value != 1 || num_symbolic_dims == 0) { resultShape.add_dim()->set_dim_value(dim_value); } else if (num_symbolic_dims == 1) { *resultShape.add_dim() = symbolic_dim; } else { resultShape.add_dim(); } } } inline void bidirectionalBroadcastShapeInference( const TensorShapeProto& shapeL, const TensorShapeProto& shapeR, TensorShapeProto& resultShape) { std::vector shapes; shapes.push_back(&shapeL); shapes.push_back(&shapeR); multidirectionalBroadcastShapeInference(shapes, resultShape); } /* Merge the dimension information from two TensorShapeProto_Dimension instances. Values are merged into target from source. If target has no dimension information, copy from source. If source has no dimension information, ignore source. If both have dimension information: - Prefer values over params. If both have values, values must match. - Prefer target param over source param if mismatched. Fail if there are mismatches in dimension values. Currently, there is no way to refine/update dimension information for the source from information available in the target. */ inline void mergeInDimensionInfo( const TensorShapeProto_Dimension& source_dim, TensorShapeProto_Dimension& target_dim, int dim_index) { // if source has value, merge into target // else if target has value, preserve it // else merge params if (source_dim.has_dim_value()) { auto source_value = source_dim.dim_value(); if (target_dim.has_dim_value()) { auto target_value = target_dim.dim_value(); if (target_value != source_value) { fail_shape_inference( "Can't merge shape info. " "Both source and target dimension have values but they differ. Source=", source_value, " Target=", target_value, " Dimension=", dim_index); } } else { target_dim.set_dim_value(source_value); } } else if (target_dim.has_dim_value()) { // if target has a value we preserve it so do nothing } else if (target_dim.has_dim_param()) { // prefer target param over source } else if (source_dim.has_dim_param()) { target_dim.set_dim_param(source_dim.dim_param()); } } /* Merge shape information from a source shape into a target shape. * merges each TensorShapeProto_Dimension separately. * prefer values over params. * If both have values, values must match. * prefer target param over source param if mismatched. * Fail if there are mismatches in number of dimensions or dimension values. */ inline void mergeInShapeInfo( const TensorShapeProto& source, TensorShapeProto& target) { auto num_source_dims = source.dim_size(); auto num_target_dims = target.dim_size(); if (num_source_dims != num_target_dims) { fail_shape_inference( "Mismatch between number of source and target dimensions. Source=", num_source_dims, " Target=", num_target_dims); } auto& source_dims = source.dim(); auto* target_dims = target.mutable_dim(); for (int i = 0, end = source_dims.size(); i < end; ++i) { auto& source_dim = source_dims.Get(i); auto& target_dim = *target_dims->Mutable(i); mergeInDimensionInfo(source_dim, target_dim, i); } } inline void mergeInShapeInfo( const TensorShapeProto& source_shape, TypeProto_Tensor& target_type) { if (target_type.has_shape()) { // merge with existing info. mergeInShapeInfo(source_shape, *target_type.mutable_shape()); } else { // copy to target (*target_type.mutable_shape()) = source_shape; } } /* Merge the shape information from two TypeProto_Tensor instances. Values are merged into target from source. If target has no shape information, copy from source. If source has no shape information, ignore source. If both have shape information: - merge each TensorShapeProto_Dimension separately. - Prefer values over params. If both have values, values must match. - Prefer target param over source param if mismatched. Fail if there are mismatches in number of dimensions or dimension values. */ inline void mergeInShapeInfo( const TypeProto_Tensor& source, TypeProto_Tensor& target) { if (source.has_shape()) mergeInShapeInfo(source.shape(), target); } // Return a copy of a type, with a specified dimension removed from its shape. inline TypeProto RemoveIthDimensionFromShape( const TypeProto& proto, int removed_dim) { TypeProto t(proto); auto mutable_shape = t.mutable_tensor_type()->mutable_shape(); mutable_shape->clear_dim(); const auto& dims = proto.tensor_type().shape().dim(); for (int j = 0, end = dims.size(); j < end; ++j) { if (j != removed_dim) (*mutable_shape->add_dim()) = dims.Get(j); } return t; } // Return a copy of a type, with specified number of dimensions removed from the // beginning. inline TypeProto RemoveDimensionsFromShape( const TypeProto& proto, int num_dimensions) { TypeProto t(proto); auto mutable_shape = t.mutable_tensor_type()->mutable_shape(); mutable_shape->clear_dim(); const auto& dims = proto.tensor_type().shape().dim(); // skip first num_dimensions for (int j = num_dimensions, end = dims.size(); j < end; ++j) { (*mutable_shape->add_dim()) = dims.Get(j); } return t; } // copied from GSL: // https://github.com/Microsoft/GSL/blob/master/include/gsl/gsl_util template static constexpr T narrow_cast(U&& u) noexcept { return static_cast(std::forward(u)); } inline void checkInputRank(InferenceContext& ctx, size_t input_index, int expected_rank) { // We check the rank only if a rank is known for the input: if (hasInputShape(ctx, input_index)) { auto rank = getInputShape(ctx, input_index).dim_size(); if (rank != expected_rank) fail_shape_inference( "Input ", input_index, " expected to have rank ", expected_rank, " but has rank ", rank); } } // Unification (between dimensions and/or shapes) is at the heart of // shape-inference. The current inference algorithm can check input // shapes/dimensions of a node and update the output shapes/dimensions. It // cannot currently update input shapes and dimensions (even though in some // contexts this inference is possible). Hence, we have the variants below to // support "const" and "mutable" dimensions/shapes in unification. inline void checkDimEquality(int64_t value1, int64_t value2) { if (value1 != value2) fail_shape_inference( "Dimension mismatch in unification between ", value1, " and ", value2); } inline void unifyDim(const Dim& dim1, const Dim& dim2) { if (dim1.has_dim_value() && dim2.has_dim_value()) checkDimEquality(dim1.dim_value(), dim2.dim_value()); } // TODO: The functionality of unifyDim is similar to that of // mergeInDimensionInfo. However, the error messages are different. Leaving this // duplication in-place to preserve error message content. inline void unifyDim(const Dim& source_dim, Dim& target_dim) { if (source_dim.has_dim_value()) { auto source_value = source_dim.dim_value(); if (target_dim.has_dim_value()) { auto target_value = target_dim.dim_value(); checkDimEquality(source_value, target_value); } else { target_dim.set_dim_value(source_value); } } else if (target_dim.has_dim_value()) { // if target has a value we preserve it. // we cannot set source dim value. } else if (target_dim.has_dim_param()) { // prefer target param over source // we cannot currently unify the dim_params } else if (source_dim.has_dim_param()) { target_dim.set_dim_param(source_dim.dim_param()); } } inline void unifyInputDim( InferenceContext& ctx, size_t input_index, int dim_index, Dim& dim) { // We unify the dimensions only if it is available for specified input: if (hasInputShape(ctx, input_index)) { auto& input_shape = getInputShape(ctx, input_index); // This shape is expected to have rank > dim_index: if (input_shape.dim_size() <= dim_index) fail_shape_inference( "Input ", input_index, " expected to have rank >", dim_index, " but has rank ", input_shape.dim_size()); const Dim& input_dim = input_shape.dim(dim_index); // Now, unify dim and input_dim: unifyDim(input_dim, dim); } } // unifyDim: unifies a dimension with a constant value. If the dimension // already has a value, we check for equality of new value with old value. inline void unifyDim(Dim& dim, int64_t value) { if (dim.has_dim_value()) { checkDimEquality(dim.dim_value(), value); } else dim.set_dim_value(value); } // target-shape = Union (target-shape, source_shape) // Example 1: same rank, different dimensions // input1 shape: (2, 3, 4, 'x') // input2 shape: (2, 'y', 5, 'x') // output shape: (2, None, None, 'x') // Example 2: different rank // input1 shape: (2, 3, 4, 'x') // input2 shape: (2, 3, 4) // output shape: None inline void UnionShapeInfo( const TensorShapeProto& source_shape, TypeProto_Tensor& target_type) { if (target_type.has_shape()) { TensorShapeProto* target_shape = target_type.mutable_shape(); auto source_rank = source_shape.dim_size(); auto target_rank = target_shape->dim_size(); if (source_rank != target_rank) { target_type.clear_shape(); return; } for (int i = 0; i < source_rank; ++i) { const auto source_dim = source_shape.dim(i); const auto target_dim = target_shape->dim(i); bool is_dims_conflict = [&](){ if (source_dim.has_dim_value()) { if (target_dim.has_dim_value() && target_dim.dim_value() == source_dim.dim_value()) { return false; } return true; } if (source_dim.has_dim_param()) { if (target_dim.has_dim_param() && target_dim.dim_param() == source_dim.dim_param()) { return false; } return true; } return (target_dim.has_dim_value() || target_dim.has_dim_param()); }(); if (is_dims_conflict && (target_dim.has_dim_value() || target_dim.has_dim_param())) { auto dim = target_shape->mutable_dim(i); dim->clear_dim_value(); dim->clear_dim_param(); } } } } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/math/0000775000000000000000000000000013655345213014044 5ustar rootrootonnx-1.7.0/onnx/defs/math/old.cc0000664000000000000000000017133713655345213015145 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include "onnx/defs/schema.h" #include "onnx/defs/tensor_proto_util.h" namespace ONNX_NAMESPACE { std::function SoftmaxFamilyDocGenerator_opset1( const char* name, const char* description) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( The operator computes the {name} ({description}) values for each layer in the batch of the given input. The input is a 2-D tensor (Tensor) of size (batch_size x input_feature_dimensions). The output tensor has the same shape and contains the {name} values of the corresponding input. Input does not need to explicitly be a 2D vector; rather, it will be coerced into one. For an arbitrary n-dimensional tensor input \in [a_0, a_1, ..., a_{k-1}, a_k, ..., a_{n-1}] and k is the axis provided, then input will be coerced into a 2-dimensional tensor with dimensions [a_0 * ... * a_{k-1}, a_k * ... * a_{n-1}]. For the default case where axis=1, this means the input tensor will be coerced into a 2D tensor of dimensions [a_0, a_1 * ... * a_{n-1}], where a_0 is often the batch size. In this situation, we must have a_0 = N and a_1 * ... * a_{n-1} = D. Each of these dimensions must be matched correctly, or else the operator will throw errors. )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{description}", description);); schema.SetDoc(doc); schema.Attr( "axis", "Describes the axis of the inputs when coerced " "to 2D; defaults to one because the 0th axis most likely describes " "the batch_size", AttributeProto::INT, static_cast(1)); schema.Input( 0, "input", "The input tensor that's coerced into a 2D matrix of size (NxD) " "as described above.", "T"); schema.Output( 0, "output", "The output values with the same " "shape as input tensor (the original size without coercion).", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput); }; } ONNX_OPERATOR_SET_SCHEMA( Softmax, 1, OpSchema().FillUsing( SoftmaxFamilyDocGenerator_opset1("softmax", "normalized exponential"))); ONNX_OPERATOR_SET_SCHEMA( LogSoftmax, 1, OpSchema().FillUsing( SoftmaxFamilyDocGenerator_opset1("logsoftmax", "log of softmax"))); ONNX_OPERATOR_SET_SCHEMA( Hardmax, 1, OpSchema().FillUsing(SoftmaxFamilyDocGenerator_opset1( "hardmax", "1 for the first maximum value, and 0 for all others"))); const char* kBroadcastDoc_old = R"DOC( If necessary the right-hand-side argument will be broadcasted to match the shape of left-hand-side argument. When broadcasting is specified, the second tensor can either be of element size 1 (including a scalar tensor and any tensor with rank equal to or smaller than the first tensor), or having its shape as a contiguous subset of the first tensor's shape. The starting of the mutually equal shape is specified by the argument "axis", and if it is not set, suffix matching is assumed. 1-dim expansion doesn't work yet. For example, the following tensor shapes are supported (with broadcast=1): shape(A) = (2, 3, 4, 5), shape(B) = (,), i.e. B is a scalar tensor shape(A) = (2, 3, 4, 5), shape(B) = (1, 1), i.e. B is an 1-element tensor shape(A) = (2, 3, 4, 5), shape(B) = (5,) shape(A) = (2, 3, 4, 5), shape(B) = (4, 5) shape(A) = (2, 3, 4, 5), shape(B) = (3, 4), with axis=1 shape(A) = (2, 3, 4, 5), shape(B) = (2), with axis=0 Attribute `broadcast=1` needs to be passed to enable broadcasting. )DOC"; std::function MathDocGenerator_old(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Performs element-wise binary {name} (with limited broadcast support). {broadcast_doc})DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{broadcast_doc}", kBroadcastDoc_old);); schema.SetDoc(doc); schema.Attr( "broadcast", "Pass 1 to enable broadcasting", AttributeProto::INT, static_cast(0)); // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the old // definition. schema.Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "axis", "If set, defines the broadcast dimensions. See doc for details.", AttributeProto::INT, OPTIONAL_VALUE); schema.Input( 0, "A", "First operand, should share the type with the second operand.", "T"); schema.Input( 1, "B", "Second operand. With broadcasting can be of smaller size than A. " "If broadcasting is disabled it should be of the same size.", "T"); schema.Output(0, "C", "Result, has same dimensions and type as A", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); }; } std::function MathDocGenerator_old_opset6(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Performs element-wise binary {name} (with limited broadcast support). {broadcast_doc})DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{broadcast_doc}", kBroadcastDoc_old);); schema.SetDoc(doc); schema.Attr( "broadcast", "Pass 1 to enable broadcasting", AttributeProto::INT, static_cast(0)); schema.Attr( "axis", "If set, defines the broadcast dimensions. See doc for details.", AttributeProto::INT, OPTIONAL_VALUE); schema.Input( 0, "A", "First operand, should share the type with the second operand.", "T"); schema.Input( 1, "B", "Second operand. With broadcasting can be of smaller size than A. " "If broadcasting is disabled it should be of the same size.", "T"); schema.Output(0, "C", "Result, has same dimensions and type as A", "T"); schema.TypeConstraint( "T", OpSchema::numeric_types_for_math_reduction(), "Constrain input and output types to high-precision numeric tensors."); schema.TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput); }; } ONNX_OPERATOR_SET_SCHEMA( Add, 1, OpSchema().FillUsing(MathDocGenerator_old("addition"))); ONNX_OPERATOR_SET_SCHEMA( Sub, 1, OpSchema().FillUsing(MathDocGenerator_old("subtraction"))); ONNX_OPERATOR_SET_SCHEMA( Mul, 1, OpSchema().FillUsing(MathDocGenerator_old("multiplication"))); ONNX_OPERATOR_SET_SCHEMA( Div, 1, OpSchema().FillUsing(MathDocGenerator_old("division"))); ONNX_OPERATOR_SET_SCHEMA( Add, 6, OpSchema().FillUsing(MathDocGenerator_old_opset6("addition"))); ONNX_OPERATOR_SET_SCHEMA( Sub, 6, OpSchema().FillUsing(MathDocGenerator_old_opset6("subtraction"))); ONNX_OPERATOR_SET_SCHEMA( Mul, 6, OpSchema().FillUsing(MathDocGenerator_old_opset6("multiplication"))); ONNX_OPERATOR_SET_SCHEMA( Div, 6, OpSchema().FillUsing(MathDocGenerator_old_opset6("division"))); static const char* Pow_ver1_doc = R"DOC( Pow takes input data (Tensor) and exponent Tensor, and produces one output data (Tensor) where the function `f(x) = x^exponent`, is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Pow, 1, OpSchema() .SetDoc(Pow_ver1_doc + std::string(kBroadcastDoc_old)) .Input(0, "X", "Input tensor of any shape, base of the exponent.", "T") .Input( 1, "Y", "Input tensor of any shape broadcastable to X shape, " "the exponent component.", "T") .Attr( "broadcast", "Pass 1 to enable broadcasting", AttributeProto::INT, static_cast(0)) .Attr( "axis", "If set, defines the broadcast dimensions. See doc for details.", AttributeProto::INT, OPTIONAL_VALUE) .Output(0, "Z", "Output tensor (same size as X)", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Pow_ver7_doc = R"DOC( Pow takes input data (Tensor) and exponent Tensor, and produces one output data (Tensor) where the function `f(x) = x^exponent`, is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Pow, 7, OpSchema() .SetDoc(std::string(Pow_ver7_doc) + GenerateBroadcastingDocMul()) .Input(0, "X", "First operand, base of the exponent.", "T") .Input(1, "Y", "Second operand, power of the exponent.", "T") .Output(0, "Z", "Output tensor (same size as X)", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); })); static const char* Neg_ver1_doc = R"DOC( Neg takes one input data (Tensor) and produces one output data (Tensor) where each element flipped sign, y = -x, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Neg, 1, OpSchema() .SetDoc(Neg_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Abs_ver1_doc = R"DOC( Absolute takes one input data (Tensor) and produces one output data (Tensor) where the absolute is, y = abs(x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Abs, 1, OpSchema() .SetDoc(Abs_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Reciprocal_ver1_doc = R"DOC( Reciprocal takes one input data (Tensor) and produces one output data (Tensor) where the reciprocal is, y = 1/x, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Reciprocal, 1, OpSchema() .SetDoc(Reciprocal_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Floor_ver1_doc = R"DOC( Floor takes one input data (Tensor) and produces one output data (Tensor) where the floor is, y = floor(x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Floor, 1, OpSchema() .SetDoc(Floor_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Ceil_ver1_doc = R"DOC( Ceil takes one input data (Tensor) and produces one output data (Tensor) where the ceil is, y = ceil(x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Ceil, 1, OpSchema() .SetDoc(Ceil_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Sqrt_ver1_doc = R"DOC( Square root takes one input data (Tensor) and produces one output data (Tensor) where the square root is, y = x^0.5, is applied to the tensor elementwise. If x is negative, then it will return NaN. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sqrt, 1, OpSchema() .SetDoc(Sqrt_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Relu_ver1_doc = R"DOC( Relu takes one input data (Tensor) and produces one output data (Tensor) where the rectified linear function, y = max(0, x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Relu, 1, OpSchema() .SetDoc(Relu_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* LeakyRelu_ver1_doc = R"DOC( LeakyRelu takes input data (Tensor) and an argument alpha, and produces one output data (Tensor) where the function `f(x) = alpha * x for x < 0`, `f(x) = x for x >= 0`, is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( LeakyRelu, 1, OpSchema() .Attr( "alpha", "Coefficient of leakage default to 0.01.", AttributeProto::FLOAT, 0.01f) .SetDoc(LeakyRelu_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Selu_ver1_doc = R"DOC( Selu takes one input data (Tensor) and produces one output data (Tensor) where the scaled exponential linear unit function, `y = gamma * (alpha * e^x - alpha) for x <= 0`, `y = gamma * x for x > 0`, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Selu, 1, OpSchema() .Attr( "alpha", "Coefficient of SELU default to 1.6732.", AttributeProto::FLOAT, 1.6732f) .Attr( "gamma", "Coefficient of SELU default to 1.0507.", AttributeProto::FLOAT, 1.0507f) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Selu_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Elu_ver1_doc = R"DOC( Elu takes one input data (Tensor) and produces one output data (Tensor) where the function `f(x) = alpha * (exp(x) - 1.) for x < 0`, `f(x) = x for x >= 0`., is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Elu, 1, OpSchema() .Attr( "alpha", "Coefficient of ELU default to 1.0.", AttributeProto::FLOAT, 1.0f) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(Elu_ver1_doc) .Input(0, "X", "1D input tensor", "T") .Output(0, "Y", "1D input tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Exp_ver1_doc = R"DOC( Calculates the exponential of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Exp, 1, OpSchema() .SetDoc(Exp_ver1_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The exponential of the input tensor computed " "element-wise", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Log_ver1_doc = R"DOC( Calculates the natural log of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Log, 1, OpSchema() .SetDoc(Log_ver1_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The natural log of the input tensor computed " "element-wise", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Tanh_ver1_doc = R"DOC( Calculates the hyperbolic tangent of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Tanh, 1, OpSchema() .SetDoc(Tanh_ver1_doc) .Input(0, "input", "1-D input tensor", "T") .Output( 0, "output", "The hyperbolic tangent values of the input tensor " "computed element-wise", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* PRelu_ver1_doc = R"DOC( PRelu takes input data (Tensor) and slope tensor as input, and produces one output data (Tensor) where the function `f(x) = slope * x for x < 0`, `f(x) = x for x >= 0`., is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( PRelu, 1, OpSchema() .SetDoc(PRelu_ver1_doc) .Input(0, "X", "Input tensor", "T") .Input( 1, "slope", "Slope tensor. If `Slope` is of size 1, the value is shared" "across different channels", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); ONNX_OPERATOR_SET_SCHEMA( PRelu, 6, OpSchema() .SetDoc(PRelu_ver1_doc) .Input(0, "X", "Input tensor", "T") .Input( 1, "slope", "Slope tensor. If `Slope` is of size 1, the value is shared" "across different channels", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* PRelu_ver7_doc = R"DOC( PRelu takes input data (Tensor) and slope tensor as input, and produces one output data (Tensor) where the function `f(x) = slope * x for x < 0`, `f(x) = x for x >= 0`., is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( PRelu, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(PRelu_ver7_doc) + GenerateBroadcastingDocUni("tensor slope", "input tensor X"))) .Input(0, "X", "Input tensor", "T") .Input( 1, "slope", "Slope tensor. The shape of slope can be smaller then first input X; " "if so, its shape must be unidirectional broadcastable to X", "T") .Output(0, "Y", "Output tensor (same size as X)", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Sigmoid_ver1_doc = R"DOC( Sigmoid takes one input data (Tensor) and produces one output data (Tensor) where the sigmoid function, y = 1 / (1 + exp(-x)), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sigmoid, 1, OpSchema() .SetDoc(Sigmoid_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* HardSigmoid_ver1_doc = R"DOC( HardSigmoid takes one input data (Tensor) and produces one output data (Tensor) where the HardSigmoid function, y = max(0, min(1, alpha * x + beta)), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( HardSigmoid, 1, OpSchema() .Attr( "alpha", "Value of alpha default to 0.2", AttributeProto::FLOAT, 0.2f) .Attr( "beta", "Value of beta default to 0.5", AttributeProto::FLOAT, 0.5f) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .SetDoc(HardSigmoid_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Max_ver1_doc = R"DOC( Element-wise max of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Max, 1, OpSchema() .SetDoc(Max_ver1_doc) .Input(0, "data_0", "List of tensors for Max.", "T", OpSchema::Variadic) .Output(0, "max", "Output tensor. Same dimension as inputs.", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Min_ver1_doc = R"DOC( Element-wise min of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Min, 1, OpSchema() .SetDoc(Min_ver1_doc) .Input(0, "data_0", "List of tensors for Min", "T", OpSchema::Variadic) .Output(0, "min", "Output tensor. Same dimension as inputs.", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Sum_ver1_doc = R"DOC( Element-wise sum of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sum, 1, OpSchema() .SetDoc(Sum_ver1_doc) .Input(0, "data_0", "List of tensors for Sum.", "T", OpSchema::Variadic) .Output(0, "sum", "Output tensor. Same dimension as inputs.", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Mean_ver1_doc = R"DOC( Element-wise mean of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Mean, 1, OpSchema() .SetDoc(Mean_ver1_doc) .Input( 0, "data_0", "List of tensors for Mean.", "T", OpSchema::Variadic) .Output(0, "mean", "Output tensor. Same dimension as inputs.", "T") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Clip_ver1_doc = R"DOC( Clip operator limits the given input within an interval. The interval is specified with arguments 'min' and 'max'. They default to numeric_limits::lowest() and numeric_limits::max() respectively. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Clip, 1, OpSchema() .SetDoc(Clip_ver1_doc) .Attr( "min", "Minimum value, under which element is replaced by min", AttributeProto::FLOAT, OPTIONAL_VALUE) .Attr( "max", "Maximum value, above which element is replaced by max", AttributeProto::FLOAT, OPTIONAL_VALUE) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .Input(0, "input", "Input tensor whose elements to be clipped", "T") .Output(0, "output", "Output tensor with clipped input elements", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Gemm_ver1_doc = R"DOC(General Matrix multiplication: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_3 Compute Y = alpha * A * B + beta * C, where input tensor A has dimension (M X K), input tensor B has dimension (K X N), input tensor C and output tensor Y have dimension (M X N). If attribute broadcast is non-zero, input tensor C will be broadcasted to match the dimension requirement. A will be transposed before doing the computation if attribute transA is non-zero, same for B and transB. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gemm, 1, OpSchema() .SetDoc(Gemm_ver1_doc) .Input(0, "A", "Input tensor A", "T") .Input(1, "B", "Input tensor B", "T") .Input(2, "C", "Input tensor C, can be inplace.", "T") .Output(0, "Y", "Output tensor.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "transA", "Whether A should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "transB", "Whether B should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "broadcast", "Whether C should be broadcasted", AttributeProto::INT, static_cast(0)) .Attr( "alpha", "Scalar multiplier for the product of input tensors A * B, the default value is 1.0.", AttributeProto::FLOAT, 1.0f) .Attr( "beta", "Scalar multiplier for input tensor C, the default value is 1.0.", AttributeProto::FLOAT, 1.0f)); static const char* Gemm_ver6_doc = R"DOC(General Matrix multiplication: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_3 Compute Y = alpha * A * B + beta * C, where input tensor A has dimension (M X K), input tensor B has dimension (K X N), input tensor C and output tensor Y have dimension (M X N). If attribute broadcast is non-zero, input tensor C will be broadcasted to match the dimension requirement. A will be transposed before doing the computation if attribute transA is non-zero, same for B and transB. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gemm, 6, OpSchema() .SetDoc(Gemm_ver6_doc) .Input(0, "A", "Input tensor A", "T") .Input(1, "B", "Input tensor B", "T") .Input(2, "C", "Input tensor C", "T") .Output(0, "Y", "Output tensor.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .Attr( "transA", "Whether A should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "transB", "Whether B should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "broadcast", "Whether C should be broadcasted", AttributeProto::INT, static_cast(0)) .Attr( "alpha", "Scalar multiplier for the product of input tensors A * B, the default value is 1.0.", AttributeProto::FLOAT, 1.0f) .Attr( "beta", "Scalar multiplier for input tensor C, the default value is 1.0.", AttributeProto::FLOAT, 1.0f) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) { auto transAAttr = ctx.getAttribute("transA"); bool transA = transAAttr ? static_cast(transAAttr->i()) != 0 : false; auto transBAttr = ctx.getAttribute("transB"); bool transB = transBAttr ? static_cast(transBAttr->i()) != 0 : false; *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = ctx.getInputType(0)->tensor_type().shape().dim(transA ? 1 : 0); *ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim() = ctx.getInputType(1)->tensor_type().shape().dim(transB ? 0 : 1); } else if ( hasInputShape(ctx, 2) && (!ctx.getAttribute("broadcast") || static_cast(ctx.getAttribute("broadcast")->i()) == 0)) { *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape() = ctx.getInputType(2)->tensor_type().shape(); } })); static const char* Gemm_ver7_doc = R"DOC(General Matrix multiplication: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_3 A' = transpose(A) if transA else A B' = transpose(B) if transB else B Compute Y = alpha * A' * B' + beta * C, where input tensor A has shape (M, K) or (K, M), input tensor B has shape (K, N) or (N, K), input tensor C is broadcastable to shape (M, N), and output tensor Y has shape (M, N). A will be transposed before doing the computation if attribute transA is non-zero, same for B and transB. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gemm, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Gemm_ver7_doc) + GenerateBroadcastingDocUni("tensor C", "tensor A * B"))) .Input( 0, "A", "Input tensor A. " "The shape of A should be (M, K) if transA is 0, " "or (K, M) if transA is non-zero.", "T") .Input( 1, "B", "Input tensor B. " "The shape of B should be (K, N) if transB is 0, " "or (N, K) if transB is non-zero.", "T") .Input( 2, "C", "Input tensor C. " "The shape of C should be unidirectional broadcastable to (M, N).", "T") .Output(0, "Y", "Output tensor of shape (M, N).", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .Attr( "transA", "Whether A should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "transB", "Whether B should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "alpha", "Scalar multiplier for the product of input tensors A * B.", AttributeProto::FLOAT, 1.0f) .Attr( "beta", "Scalar multiplier for input tensor C.", AttributeProto::FLOAT, 1.0f) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) { auto transAAttr = ctx.getAttribute("transA"); bool transA = transAAttr ? static_cast(transAAttr->i()) != 0 : false; auto transBAttr = ctx.getAttribute("transB"); bool transB = transBAttr ? static_cast(transBAttr->i()) != 0 : false; auto& first_input_shape = getInputShape(ctx, 0); auto& second_input_shape = getInputShape(ctx, 1); if (first_input_shape.dim_size() != 2) { fail_shape_inference("First input does not have rank 2"); } if (second_input_shape.dim_size() != 2) { fail_shape_inference("Second input does not have rank 2"); } updateOutputShape( ctx, 0, {first_input_shape.dim(transA ? 1 : 0), second_input_shape.dim(transB ? 0 : 1)}); } })); static const char* Gemm_ver9_doc = R"DOC(General Matrix multiplication: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_3 A' = transpose(A) if transA else A B' = transpose(B) if transB else B Compute Y = alpha * A' * B' + beta * C, where input tensor A has shape (M, K) or (K, M), input tensor B has shape (K, N) or (N, K), input tensor C is broadcastable to shape (M, N), and output tensor Y has shape (M, N). A will be transposed before doing the computation if attribute transA is non-zero, same for B and transB. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gemm, 9, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Gemm_ver9_doc) + GenerateBroadcastingDocUni("tensor C", "tensor A * B"))) .Input( 0, "A", "Input tensor A. " "The shape of A should be (M, K) if transA is 0, " "or (K, M) if transA is non-zero.", "T") .Input( 1, "B", "Input tensor B. " "The shape of B should be (K, N) if transB is 0, " "or (N, K) if transB is non-zero.", "T") .Input( 2, "C", "Input tensor C. " "The shape of C should be unidirectional broadcastable to (M, N).", "T") .Output(0, "Y", "Output tensor of shape (M, N).", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(uint32)", "tensor(uint64)", "tensor(int32)", "tensor(int64)"}, "Constrain input and output types to float/int tensors.") .Attr( "transA", "Whether A should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "transB", "Whether B should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "alpha", "Scalar multiplier for the product of input tensors A * B.", AttributeProto::FLOAT, 1.0f) .Attr( "beta", "Scalar multiplier for input tensor C.", AttributeProto::FLOAT, 1.0f) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) { auto transAAttr = ctx.getAttribute("transA"); bool transA = transAAttr ? static_cast(transAAttr->i()) != 0 : false; auto transBAttr = ctx.getAttribute("transB"); bool transB = transBAttr ? static_cast(transBAttr->i()) != 0 : false; auto& first_input_shape = getInputShape(ctx, 0); auto& second_input_shape = getInputShape(ctx, 1); if (first_input_shape.dim_size() != 2) { fail_shape_inference("First input does not have rank 2"); } if (second_input_shape.dim_size() != 2) { fail_shape_inference("Second input does not have rank 2"); } updateOutputShape( ctx, 0, {first_input_shape.dim(transA ? 1 : 0), second_input_shape.dim(transB ? 0 : 1)}); } })); static const char* Max_ver6_doc = R"DOC( Element-wise max of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Max, 6, OpSchema() .SetDoc(Max_ver6_doc) .Input(0, "data_0", "List of tensors for Max.", "T", OpSchema::Variadic) .Output(0, "max", "Output tensor. Same dimension as inputs.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Min_ver6_doc = R"DOC( Element-wise min of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Min, 6, OpSchema() .SetDoc(Min_ver6_doc) .Input(0, "data_0", "List of tensors for Min", "T", OpSchema::Variadic) .Output(0, "min", "Output tensor. Same dimension as inputs.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Sum_ver6_doc = R"DOC( Element-wise sum of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sum, 6, OpSchema() .SetDoc(Sum_ver6_doc) .Input(0, "data_0", "List of tensors for Sum.", "T", OpSchema::Variadic) .Output(0, "sum", "Output tensor. Same dimension as inputs.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Mean_ver6_doc = R"DOC( Element-wise mean of each of the input tensors. All inputs and outputs must have the same shape and data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Mean, 6, OpSchema() .SetDoc(Mean_ver6_doc) .Input( 0, "data_0", "List of tensors for Mean.", "T", OpSchema::Variadic) .Output(0, "mean", "Output tensor. Same dimension as inputs.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* MatMul_ver1_doc = R"DOC( Matrix product that behaves like numpy.matmul: https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.matmul.html )DOC"; ONNX_OPERATOR_SET_SCHEMA( MatMul, 1, OpSchema() .Input(0, "A", "N-dimensional matrix A", "T") .Input(1, "B", "N-dimensional matrix B", "T") .Output(0, "Y", "Matrix multiply results from A * B", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .SetDoc(MatMul_ver1_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 2)) { return; } const auto shape0 = ctx.getInputType(0)->tensor_type().shape(); const auto shape1 = ctx.getInputType(1)->tensor_type().shape(); if (shape0.dim_size() == 0 || shape1.dim_size() == 0) { fail_shape_inference("Input tensors of wrong rank (0)."); } TensorShapeProto shapeL, shapeR; // First promote each shape to at least rank-2. This logic is // specific to matmul, not generic broadcasting. { if (shape0.dim_size() == 1) { shapeL.add_dim()->set_dim_value(1); *shapeL.add_dim() = shape0.dim(0); } else { *shapeL.mutable_dim() = shape0.dim(); } if (shape1.dim_size() == 1) { *shapeR.add_dim() = shape1.dim(0); shapeR.add_dim()->set_dim_value(1); } else { *shapeR.mutable_dim() = shape1.dim(); } } // Check for compatible matrix multiply dimensions { auto dimL = shapeL.dim(shapeL.dim_size() - 1); auto dimR = shapeR.dim(shapeR.dim_size() - 2); if (dimL.has_dim_value() && dimR.has_dim_value() && dimL.dim_value() != dimR.dim_value()) { fail_shape_inference( "Incompatible dimensions for matrix multiplication"); ; } } TensorShapeProto resultShape; // Now call out to generic multidimensional broadcasting for // the broadcastable prefixes. { TensorShapeProto prefixShapeL, prefixShapeR; for (int i = 0; i < shapeL.dim_size() - 2; ++i) { *prefixShapeL.add_dim() = shapeL.dim(i); } for (int i = 0; i < shapeR.dim_size() - 2; ++i) { *prefixShapeR.add_dim() = shapeR.dim(i); } bidirectionalBroadcastShapeInference( prefixShapeL, prefixShapeR, resultShape); } // Back to matmul-specific. Add the trailing dimensions back in. { if (shape0.dim_size() != 1) { *resultShape.add_dim() = shapeL.dim(shapeL.dim_size() - 2); } if (shape1.dim_size() != 1) { *resultShape.add_dim() = shapeR.dim(shapeR.dim_size() - 1); } } *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape() = resultShape; })); static const char* TopK_ver1_doc = R"DOC( Retrieve the top-K elements along a specified axis. Given an input tensor of shape [a_1, a_2, ..., a_n, r] and integer argument k, return two outputs: -Value tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] which contains the values of the top k elements along the specified axis -Index tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] which contains the indices of the top k elements (original indices from the input tensor). Given two equivalent values, this operator uses the indices along the axis as a tiebreaker. That is, the element with the lower index will appear first. )DOC"; ONNX_OPERATOR_SET_SCHEMA( TopK, 1, OpSchema() .SetDoc(TopK_ver1_doc) .Input(0, "X", "Tensor of shape [a_1, a_2, ..., a_n, r]", "T") .Output( 0, "Values", "Tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] " "containing top K values from the input tensor", "T") .Output( 1, "Indices", "Tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] " "containing the corresponding input tensor indices for the top K " "values.", "I") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64") .Attr( "k", "Number of top elements to retrieve", AttributeProto::INT, true) .Attr( "axis", "Dimension on which to do the sort.", AttributeProto::INT, static_cast(-1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference: propagateElemTypeFromInputToOutput(ctx, 0, 0); updateOutputElemType(ctx, 1, TensorProto::INT64); // Shape inference: if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); int64_t rank = input_shape.dim_size(); int64_t axis = getAttribute(ctx, "axis", -1); if (axis < 0) axis += rank; if (axis < 0 || axis >= rank) fail_shape_inference("Invalid value for attribute axis"); int64_t k = getAttribute(ctx, "k", -1); if (k <= 0) fail_shape_inference("Invalid value for attribute k"); // TODO: unclear what results should be if axis has less than k // elements. TensorShapeProto result_shape = input_shape; result_shape.mutable_dim(static_cast(axis))->set_dim_value(k); updateOutputShape(ctx, 0, result_shape); updateOutputShape(ctx, 1, result_shape); })); static const char* TopK_ver10_doc = R"DOC( Retrieve the top-K elements along a specified axis. Given an input tensor of shape [a_1, a_2, ..., a_n, r] and integer argument k, return two outputs: -Value tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] which contains the values of the top k elements along the specified axis -Index tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] which contains the indices of the top k elements (original indices from the input tensor). Given two equivalent values, this operator uses the indices along the axis as a tiebreaker. That is, the element with the lower index will appear first. )DOC"; ONNX_OPERATOR_SET_SCHEMA( TopK, 10, OpSchema() .SetDoc(TopK_ver10_doc) .Input(0, "X", "Tensor of shape [a_1, a_2, ..., a_n, r]", "T") .Input( 1, "K", "A 1-D tensor containing a single positive value corresponding to the number of top elements to retrieve", "tensor(int64)") .Output( 0, "Values", "Tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] " "containing top K values from the input tensor", "T") .Output( 1, "Indices", "Tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] " "containing the corresponding input tensor indices for the top K " "values.", "I") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64") .Attr( "axis", "Dimension on which to do the sort.", AttributeProto::INT, static_cast(-1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference: propagateElemTypeFromInputToOutput(ctx, 0, 0); updateOutputElemType(ctx, 1, TensorProto::INT64); // Shape inference: if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); int64_t rank = input_shape.dim_size(); int64_t axis = getAttribute(ctx, "axis", -1); if (axis < 0) axis += rank; if (axis < 0 || axis >= rank) fail_shape_inference("Invalid value for attribute axis"); const auto& axis_dim = input_shape.dim(static_cast(axis)); const auto* k = ctx.getInputData(1); // Infer output shape if: // (1) 'K' is available // (2) axis_dim has dim value // Othewise cannot reliably compute output shape as axis dim value is // unknown and hence cannot determine if axis dim value >= k (which // should be enforced) if (nullptr != k && axis_dim.has_dim_value()) { int64_t k_value = 0; if (k->dims_size() != 1 || k->dims(0) != 1) fail_shape_inference( "K input must be a one-dimensional tensor of size 1."); if (k->data_type() == TensorProto::INT64) { const auto& data = ParseData(k); k_value = data[0]; } else fail_shape_inference("K input must be of type int64."); if (axis_dim.dim_value() < k_value) fail_shape_inference( "Axis has less than the requested k elements."); TensorShapeProto result_shape = input_shape; result_shape.mutable_dim(static_cast(axis)) ->set_dim_value(k_value); updateOutputShape(ctx, 0, result_shape); updateOutputShape(ctx, 1, result_shape); return; } // Infer output shapes' rank in any case auto* output_shape_0 = getOutputShape(ctx, 0); auto* output_shape_1 = getOutputShape(ctx, 1); for (int i = 0; i < input_shape.dim_size(); ++i) { output_shape_0->add_dim(); output_shape_1->add_dim(); } return; })); static const char* Clip_ver6_doc = R"DOC( Clip operator limits the given input within an interval. The interval is specified with arguments 'min' and 'max'. They default to numeric_limits::lowest() and numeric_limits::max() respectively. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Clip, 6, OpSchema() .SetDoc(Clip_ver6_doc) .Attr( "min", "Minimum value, under which element is replaced by min", AttributeProto::FLOAT, std::numeric_limits::lowest()) .Attr( "max", "Maximum value, above which element is replaced by max", AttributeProto::FLOAT, std::numeric_limits::max()) .Input(0, "input", "Input tensor whose elements to be clipped", "T") .Output(0, "output", "Output tensor with clipped input elements", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Clip_ver11_doc = R"DOC( Clip operator limits the given input within an interval. The interval is specified by the inputs 'min' and 'max'. They default to numeric_limits::lowest() and numeric_limits::max(), respectively. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Clip, 11, OpSchema() .SetDoc(Clip_ver11_doc) .Input(0, "input", "Input tensor whose elements to be clipped", "T") .Input( 1, "min", "Minimum value, under which element is replaced by min. " "It must be a scalar(tensor of empty shape).", "T", OpSchema::Optional) .Input( 2, "max", "Maximum value, above which element is replaced by max. " "It must be a scalar(tensor of empty shape).", "T", OpSchema::Optional) .Output(0, "output", "Output tensor with clipped input elements", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); std::function ElementwiseMultiOpDocGenerator_old( const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( Element-wise {name} of each of the input tensors (with Numpy-style broadcasting support). All inputs and outputs must have the same data type. {broadcast_doc} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll( doc, "{broadcast_doc}", GenerateBroadcastingDocMul().c_str());); schema.SetDoc(doc); schema.Input( 0, "data_0", "List of tensors for " + std::string(name) + ".", "T", OpSchema::Variadic); schema.Output(0, name, "Output tensor.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); int num_inputs = static_cast(ctx.getNumInputs()); std::vector shapes; for (int i = 0; i < num_inputs; ++i) { auto input_type = ctx.getInputType(i); if (nullptr == input_type || !input_type->has_tensor_type() || !input_type->tensor_type().has_shape()) { return; } shapes.push_back(&input_type->tensor_type().shape()); } multidirectionalBroadcastShapeInference( shapes, *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); }); }; } ONNX_OPERATOR_SET_SCHEMA( Max, 8, OpSchema().FillUsing(ElementwiseMultiOpDocGenerator_old("max"))); ONNX_OPERATOR_SET_SCHEMA( Min, 8, OpSchema().FillUsing(ElementwiseMultiOpDocGenerator_old("min"))); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/math/defs.cc0000664000000000000000000026373413655345213015313 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include "onnx/defs/function.h" #include "onnx/defs/schema.h" #include "onnx/defs/tensor_proto_util.h" namespace ONNX_NAMESPACE { std::function MathDocGenerator(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( Performs element-wise binary {name} (with Numpy-style broadcasting support). {broadcast_doc} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll( doc, "{broadcast_doc}", GenerateBroadcastingDocMul().c_str());); schema.SetDoc(doc); schema.Input(0, "A", "First operand.", "T"); schema.Input(1, "B", "Second operand.", "T"); schema.Output(0, "C", "Result, has same element type as two inputs", "T"); schema.TypeConstraint( "T", OpSchema::numeric_types_for_math_reduction(), "Constrain input and output types to high-precision numeric tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); }); }; } std::function SoftmaxFamilyDocGenerator( const char* name, const char* description) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( The operator computes the {name} ({description}) values for each layer in the batch of the given input. The input does not need to explicitly be a 2D vector; rather, it will be coerced into one. For an arbitrary n-dimensional tensor input \in [a_0, a_1, ..., a_{k-1}, a_k, ..., a_{n-1}] and k is the axis provided, then input will be coerced into a 2-dimensional tensor with dimensions [a_0 * ... * a_{k-1}, a_k * ... * a_{n-1}]. For the default case where axis=1, this means the input tensor will be coerced into a 2D tensor of dimensions [a_0, a_1 * ... * a_{n-1}], where a_0 is often the batch size. In this situation, we must have a_0 = N and a_1 * ... * a_{n-1} = D. Each of these dimensions must be matched correctly, or else the operator will throw errors. The output tensor has the same shape and contains the {name} values of the corresponding input. )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{description}", description);); schema.SetDoc(doc); schema.Attr( "axis", "Describes the axis of the inputs when coerced " "to 2D; defaults to one because the 0th axis most likely describes " "the batch_size. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(input).", AttributeProto::INT, static_cast(1)); schema.Input( 0, "input", "The input tensor that's coerced into a 2D matrix of size (NxD) " "as described above.", "T"); schema.Output( 0, "output", "The output values with the same " "shape as input tensor (the original size without coercion).", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference starts if (!hasNInputShapes(ctx, 1)) { return; } // Validate the value of 'axis' const TensorShapeProto& input_shape = ctx.getInputType(0)->tensor_type().shape(); int r = input_shape.dim_size(); int axis = static_cast(getAttribute(ctx, "axis", 1)); if (axis < -r || axis >= r) { fail_shape_inference( "'axis' must be in [", -r, " , ", (r - 1), "]. Its actual value is: ", axis); } // Shape inference propagateShapeFromInputToOutput(ctx, 0, 0); }); }; } ONNX_OPERATOR_SET_SCHEMA( Add, 7, OpSchema().FillUsing(MathDocGenerator("addition"))); ONNX_OPERATOR_SET_SCHEMA( Sub, 7, OpSchema().FillUsing(MathDocGenerator("subtraction"))); static const char* Mod_doc = R"DOC( Performs element-wise binary modulus (with Numpy-style broadcasting support). The sign of the remainder is the same as that of the Divisor. Mod operator can also behave like C fmod() or numpy.fmod. In this case, the sign of the remainder however, will be the same as the Dividend (in contrast to integer mod). To force a behavior like numpy.fmod() an 'fmod' Attribute is provided. This attribute is set to 0 by default causing the behavior to be like integer mod. Setting this attribute to 1 causes the remainder to be calculated similar to that of numpy.fmod(). If the input type is floating point, then `fmod` attribute must be set to 1. In case of dividend being zero, the results will be platform dependent. This operator supports **multidirectional (i.e., Numpy-style) broadcasting**; for more details please check [the doc](Broadcasting.md). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Mod, 10, OpSchema() .SetDoc(Mod_doc) .Attr( "fmod", "Whether the operator should behave like fmod (default=0 meaning it will do integer mods); Set this to 1 to force fmod treatment", AttributeProto::INT, static_cast(0)) .Input(0, "A", "Dividend tensor", "T") .Input(1, "B", "Divisor tensor", "T") .Output(0, "C", "Remainder tensor", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to high-precision numeric tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); })); ONNX_OPERATOR_SET_SCHEMA( Mul, 7, OpSchema().FillUsing(MathDocGenerator("multiplication"))); ONNX_OPERATOR_SET_SCHEMA( Div, 7, OpSchema().FillUsing(MathDocGenerator("division"))); static const char* Neg_ver6_doc = R"DOC( Neg takes one input data (Tensor) and produces one output data (Tensor) where each element flipped sign, y = -x, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Neg, 6, OpSchema() .SetDoc(Neg_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float)", "tensor(int32)", "tensor(int8)", "tensor(int16)", "tensor(int64)", "tensor(float16)", "tensor(double)"}, "Constrain input and output types to signed numeric tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Abs_ver6_doc = R"DOC( Absolute takes one input data (Tensor) and produces one output data (Tensor) where the absolute is, y = abs(x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Abs, 6, OpSchema() .SetDoc(Abs_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Reciprocal_ver6_doc = R"DOC( Reciprocal takes one input data (Tensor) and produces one output data (Tensor) where the reciprocal is, y = 1/x, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Reciprocal, 6, OpSchema() .SetDoc(Reciprocal_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Floor_ver6_doc = R"DOC( Floor takes one input data (Tensor) and produces one output data (Tensor) where the floor is, y = floor(x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Floor, 6, OpSchema() .SetDoc(Floor_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Ceil_ver6_doc = R"DOC( Ceil takes one input data (Tensor) and produces one output data (Tensor) where the ceil is, y = ceil(x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Ceil, 6, OpSchema() .SetDoc(Ceil_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Sqrt_ver6_doc = R"DOC( Square root takes one input data (Tensor) and produces one output data (Tensor) where the square root is, y = x^0.5, is applied to the tensor elementwise. If x is negative, then it will return NaN. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sqrt, 6, OpSchema() .SetDoc(Sqrt_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Relu_ver6_doc = R"DOC( Relu takes one input data (Tensor) and produces one output data (Tensor) where the rectified linear function, y = max(0, x), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Relu, 6, OpSchema() .SetDoc(Relu_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* LeakyRelu_ver6_doc = R"DOC( LeakyRelu takes input data (Tensor) and an argument alpha, and produces one output data (Tensor) where the function `f(x) = alpha * x for x < 0`, `f(x) = x for x >= 0`, is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( LeakyRelu, 6, OpSchema() .Attr("alpha", "Coefficient of leakage.", AttributeProto::FLOAT, 0.01f) .SetDoc(LeakyRelu_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* ThresholdedRelu_ver10_doc = R"DOC( ThresholdedRelu takes one input data (Tensor) and produces one output data (Tensor) where the rectified linear function, y = x for x > alpha, y = 0 otherwise, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( ThresholdedRelu, 10, OpSchema() .SetDoc(ThresholdedRelu_ver10_doc) .Attr("alpha", "Threshold value", AttributeProto::FLOAT, 1.0f) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Selu_ver6_doc = R"DOC( Selu takes one input data (Tensor) and produces one output data (Tensor) where the scaled exponential linear unit function, `y = gamma * (alpha * e^x - alpha) for x <= 0`, `y = gamma * x for x > 0`, is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Selu, 6, OpSchema() .Attr( "alpha", "Coefficient of SELU default to 1.67326319217681884765625 " "(i.e., float32 approximation of 1.6732632423543772848170429916717).", AttributeProto::FLOAT, 1.67326319217681884765625f) .Attr( "gamma", "Coefficient of SELU default to 1.05070102214813232421875 " "(i.e., float32 approximation of 1.0507009873554804934193349852946).", AttributeProto::FLOAT, 1.05070102214813232421875f) .SetDoc(Selu_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Elu_ver6_doc = R"DOC( Elu takes one input data (Tensor) and produces one output data (Tensor) where the function `f(x) = alpha * (exp(x) - 1.) for x < 0`, `f(x) = x for x >= 0`., is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Elu, 6, OpSchema() .Attr("alpha", "Coefficient of ELU.", AttributeProto::FLOAT, 1.0f) .SetDoc(Elu_ver6_doc) .Input(0, "X", "1D input tensor", "T") .Output(0, "Y", "1D input tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* celu_ver12_doc = R"DOC( Continuously Differentiable Exponential Linear Units: Perform the linear unit element-wise on the input tensor X using formula: ``` max(0,x) + min(0,alpha*(exp(x/alpha)-1)) ``` )DOC"; static float celu_default_alpha = 1.0; TensorProto ToDimensionOneFloatTensor(float value) { auto t = ToTensor(std::vector({value})); t.add_dims(1); return t; } bool BuildContextDependentFunctionBodyCelu( const FunctionBodyBuildContext& ctx, const OpSchema& schema, FunctionProto& functionProto) { std::vector body; float alpha = ctx.getAttribute("alpha") != nullptr ? ctx.getAttribute("alpha")->f() : celu_default_alpha; body.push_back({{"alpha"}, "Constant", {}, {MakeAttribute("value", ToDimensionOneFloatTensor(alpha))}}); body.push_back({{"X_alpha"}, "Div", {"X", "alpha"}}); body.push_back({{"Elu_Result"}, "Elu", {"X_alpha"}, {{"alpha", 1.f}}}); body.push_back({{"Y"}, "Mul", {"alpha", "Elu_Result"}}); auto func_nodes = FunctionBodyHelper::BuildNodes(body); for (const auto& node : func_nodes) { auto new_node = functionProto.add_node(); new_node->CopyFrom(node); } schema.BuildFunction(functionProto); return true; } ONNX_OPERATOR_SET_SCHEMA( Celu, 12, OpSchema() .SetDoc(celu_ver12_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .Attr( "alpha", "The Alpha value in Celu formula which control the shape of " "the unit. The default value is 1.0.", AttributeProto::FLOAT, celu_default_alpha) .TypeConstraint( "T", {"tensor(float)"}, "Constrain input and output types to float32 tensors.") .SetContextDependentFunctionBodyBuilder(BuildContextDependentFunctionBodyCelu) .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Exp_ver6_doc = R"DOC( Calculates the exponential of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Exp, 6, OpSchema() .SetDoc(Exp_ver6_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The exponential of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Log_ver6_doc = R"DOC( Calculates the natural log of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Log, 6, OpSchema() .SetDoc(Log_ver6_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The natural log of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Tanh_ver6_doc = R"DOC( Calculates the hyperbolic tangent of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Tanh, 6, OpSchema() .SetDoc(Tanh_ver6_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The hyperbolic tangent values of the input tensor " "computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Pow_ver12_doc = R"DOC( Pow takes input data (Tensor) and exponent Tensor, and produces one output data (Tensor) where the function `f(x) = x^exponent`, is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Pow, 12, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Pow_ver12_doc) + GenerateBroadcastingDocMul())) .Input(0, "X", "First operand, base of the exponent.", "T") .Input(1, "Y", "Second operand, power of the exponent.", "T1") .Output(0, "Z", "Output tensor (same size as X)", "T") .TypeConstraint( "T", {"tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input X and output types to float/int tensors.") .TypeConstraint( "T1", {"tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input Y types to float/int tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); })); static const char* PRelu_ver9_doc = R"DOC( PRelu takes input data (Tensor) and slope tensor as input, and produces one output data (Tensor) where the function `f(x) = slope * x for x < 0`, `f(x) = x for x >= 0`., is applied to the data tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( PRelu, 9, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(PRelu_ver9_doc) + GenerateBroadcastingDocUni("tensor slope", "input tensor X"))) .Input(0, "X", "Input tensor", "T") .Input( 1, "slope", "Slope tensor. The shape of slope can be smaller then first input X; " "if so, its shape must be unidirectional broadcastable to X", "T") .Output(0, "Y", "Output tensor (same size as X)", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(uint32)", "tensor(uint64)", "tensor(int32)", "tensor(int64)"}, "Constrain input and output types to float/int tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Sigmoid_ver6_doc = R"DOC( Sigmoid takes one input data (Tensor) and produces one output data (Tensor) where the sigmoid function, y = 1 / (1 + exp(-x)), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sigmoid, 6, OpSchema() .SetDoc(Sigmoid_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* HardSigmoid_ver6_doc = R"DOC( HardSigmoid takes one input data (Tensor) and produces one output data (Tensor) where the HardSigmoid function, y = max(0, min(1, alpha * x + beta)), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( HardSigmoid, 6, OpSchema() .Attr("alpha", "Value of alpha.", AttributeProto::FLOAT, 0.2f) .Attr("beta", "Value of beta.", AttributeProto::FLOAT, 0.5f) .SetDoc(HardSigmoid_ver6_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); // Generate opschema for element-wise ops. Leaves type constraint "T" // unspecified. std::function ElementwiseMultiOpDocGenerator( const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( Element-wise {name} of each of the input tensors (with Numpy-style broadcasting support). All inputs and outputs must have the same data type. {broadcast_doc} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll( doc, "{broadcast_doc}", GenerateBroadcastingDocMul().c_str());); schema.SetDoc(doc); schema.Input( 0, "data_0", "List of tensors for " + std::string(name) + ".", "T", OpSchema::Variadic); schema.Output(0, name, "Output tensor.", "T"); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); int num_inputs = static_cast(ctx.getNumInputs()); std::vector shapes; for (int i = 0; i < num_inputs; ++i) { auto input_type = ctx.getInputType(i); if (nullptr == input_type || !input_type->has_tensor_type() || !input_type->tensor_type().has_shape()) { return; } shapes.push_back(&input_type->tensor_type().shape()); } multidirectionalBroadcastShapeInference( shapes, *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); }); }; } ONNX_OPERATOR_SET_SCHEMA( Max, 12, OpSchema() .FillUsing(ElementwiseMultiOpDocGenerator("max")) .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to numeric tensors.")); ONNX_OPERATOR_SET_SCHEMA( Min, 12, OpSchema() .FillUsing(ElementwiseMultiOpDocGenerator("min")) .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to numeric tensors.")); ONNX_OPERATOR_SET_SCHEMA( Sum, 8, OpSchema() .FillUsing(ElementwiseMultiOpDocGenerator("sum")) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); ONNX_OPERATOR_SET_SCHEMA( Mean, 8, OpSchema() .FillUsing(ElementwiseMultiOpDocGenerator("mean")) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Clip_ver12_doc = R"DOC( Clip operator limits the given input within an interval. The interval is specified by the inputs 'min' and 'max'. They default to numeric_limits::lowest() and numeric_limits::max(), respectively. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Clip, 12, OpSchema() .SetDoc(Clip_ver12_doc) .Input(0, "input", "Input tensor whose elements to be clipped", "T") .Input( 1, "min", "Minimum value, under which element is replaced by min. " "It must be a scalar(tensor of empty shape).", "T", OpSchema::Optional) .Input( 2, "max", "Maximum value, above which element is replaced by max. " "It must be a scalar(tensor of empty shape).", "T", OpSchema::Optional) .Output(0, "output", "Output tensor with clipped input elements", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); ONNX_OPERATOR_SET_SCHEMA( Softmax, 11, OpSchema().FillUsing( SoftmaxFamilyDocGenerator("softmax", "normalized exponential"))); ONNX_OPERATOR_SET_SCHEMA( LogSoftmax, 11, OpSchema().FillUsing( SoftmaxFamilyDocGenerator("logsoftmax", "log of softmax"))); ONNX_OPERATOR_SET_SCHEMA( Hardmax, 11, OpSchema().FillUsing(SoftmaxFamilyDocGenerator( "hardmax", "1 for the first maximum value, and 0 for all others"))); static const char* Softsign_ver1_doc = R"DOC( Calculates the softsign (x/(1+|x|)) of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Softsign, 1, OpSchema() .SetDoc(Softsign_ver1_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The softsign (x/(1+|x|)) values of the input tensor computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Softplus_ver1_doc = R"DOC( Softplus takes one input data (Tensor) and produces one output data (Tensor) where the softplus function, y = ln(exp(x) + 1), is applied to the tensor elementwise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Softplus, 1, OpSchema() .SetDoc(Softplus_ver1_doc) .Input(0, "X", "1D input tensor", "T") .Output(0, "Y", "1D input tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Gemm_ver11_doc = R"DOC(General Matrix multiplication: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms#Level_3 A' = transpose(A) if transA else A B' = transpose(B) if transB else B Compute Y = alpha * A' * B' + beta * C, where input tensor A has shape (M, K) or (K, M), input tensor B has shape (K, N) or (N, K), input tensor C is broadcastable to shape (M, N), and output tensor Y has shape (M, N). A will be transposed before doing the computation if attribute transA is non-zero, same for B and transB. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Gemm, 11, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Gemm_ver11_doc) + GenerateBroadcastingDocUni("tensor C", "tensor A * B") + "\n" + GenerateOptionalArgumentsDoc())) .Input( 0, "A", "Input tensor A. " "The shape of A should be (M, K) if transA is 0, " "or (K, M) if transA is non-zero.", "T") .Input( 1, "B", "Input tensor B. " "The shape of B should be (K, N) if transB is 0, " "or (N, K) if transB is non-zero.", "T") .Input( 2, "C", "Optional input tensor C. " "If not specified, the computation is done as if C is a scalar 0. " "The shape of C should be unidirectional broadcastable to (M, N).", "T", OpSchema::Optional) .Output(0, "Y", "Output tensor of shape (M, N).", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(uint32)", "tensor(uint64)", "tensor(int32)", "tensor(int64)"}, "Constrain input and output types to float/int tensors.") .Attr( "transA", "Whether A should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "transB", "Whether B should be transposed", AttributeProto::INT, static_cast(0)) .Attr( "alpha", "Scalar multiplier for the product of input tensors A * B.", AttributeProto::FLOAT, 1.0f) .Attr( "beta", "Scalar multiplier for input tensor C.", AttributeProto::FLOAT, 1.0f) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasNInputShapes(ctx, 2)) { auto transAAttr = ctx.getAttribute("transA"); bool transA = transAAttr ? static_cast(transAAttr->i()) != 0 : false; auto transBAttr = ctx.getAttribute("transB"); bool transB = transBAttr ? static_cast(transBAttr->i()) != 0 : false; auto& first_input_shape = getInputShape(ctx, 0); auto& second_input_shape = getInputShape(ctx, 1); if (first_input_shape.dim_size() != 2) { fail_shape_inference("First input does not have rank 2"); } if (second_input_shape.dim_size() != 2) { fail_shape_inference("Second input does not have rank 2"); } updateOutputShape( ctx, 0, {first_input_shape.dim(transA ? 1 : 0), second_input_shape.dim(transB ? 0 : 1)}); } })); void matmulShapeInference( ONNX_NAMESPACE::InferenceContext& ctx, int input1Idx, int input2Idx) { if (!hasInputShape(ctx, input1Idx) || !hasInputShape(ctx, input2Idx)) { return; } const auto shape0 = ctx.getInputType(input1Idx)->tensor_type().shape(); const auto shape1 = ctx.getInputType(input2Idx)->tensor_type().shape(); if (shape0.dim_size() == 0 || shape1.dim_size() == 0) { fail_shape_inference("Input tensors of wrong rank (0)."); } ONNX_NAMESPACE::TensorShapeProto shapeL, shapeR; // First promote each shape to at least rank-2. This logic is // specific to matmul, not generic broadcasting. { if (shape0.dim_size() == 1) { shapeL.add_dim()->set_dim_value(1); *shapeL.add_dim() = shape0.dim(0); } else { *shapeL.mutable_dim() = shape0.dim(); } if (shape1.dim_size() == 1) { *shapeR.add_dim() = shape1.dim(0); shapeR.add_dim()->set_dim_value(1); } else { *shapeR.mutable_dim() = shape1.dim(); } } // Check for compatible matrix multiply dimensions { auto dimL = shapeL.dim(shapeL.dim_size() - 1); auto dimR = shapeR.dim(shapeR.dim_size() - 2); if (dimL.has_dim_value() && dimR.has_dim_value() && dimL.dim_value() != dimR.dim_value()) { fail_shape_inference("Incompatible dimensions for matrix multiplication"); } } ONNX_NAMESPACE::TensorShapeProto resultShape; // Now call out to generic multidimensional broadcasting for // the broadcastable prefixes. { ONNX_NAMESPACE::TensorShapeProto prefixShapeL, prefixShapeR; for (int i = 0; i < shapeL.dim_size() - 2; ++i) { *prefixShapeL.add_dim() = shapeL.dim(i); } for (int i = 0; i < shapeR.dim_size() - 2; ++i) { *prefixShapeR.add_dim() = shapeR.dim(i); } bidirectionalBroadcastShapeInference( prefixShapeL, prefixShapeR, resultShape); } // Back to matmul-specific. Add the trailing dimensions back in. { if (shape0.dim_size() != 1) { *resultShape.add_dim() = shapeL.dim(shapeL.dim_size() - 2); } if (shape1.dim_size() != 1) { *resultShape.add_dim() = shapeR.dim(shapeR.dim_size() - 1); } } *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape() = resultShape; } static const char* MatMul_ver9_doc = R"DOC( Matrix product that behaves like numpy.matmul: https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.matmul.html )DOC"; ONNX_OPERATOR_SET_SCHEMA( MatMul, 9, OpSchema() .Input(0, "A", "N-dimensional matrix A", "T") .Input(1, "B", "N-dimensional matrix B", "T") .Output(0, "Y", "Matrix multiply results from A * B", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(uint32)", "tensor(uint64)", "tensor(int32)", "tensor(int64)"}, "Constrain input and output types to float/int tensors.") .SetDoc(MatMul_ver9_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); matmulShapeInference(ctx, 0, 1); })); static const char* TopK_ver11_doc = R"DOC( Retrieve the top-K largest or smallest elements along a specified axis. Given an input tensor of shape [a_1, a_2, ..., a_n, r] and integer argument k, return two outputs: -Value tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] which contains the values of the top k elements along the specified axis -Index tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] which contains the indices of the top k elements (original indices from the input tensor). If "largest" is 1 (the default value) then the k largest elements are returned. If "sorted" is 1 (the default value) then the resulting k elements will be sorted. If "sorted" is 0, order of returned 'Values' and 'Indices' are undefined. Given two equivalent values, this operator uses the indices along the axis as a tiebreaker. That is, the element with the lower index will appear first. )DOC"; ONNX_OPERATOR_SET_SCHEMA( TopK, 11, OpSchema() .SetDoc(TopK_ver11_doc) .Input(0, "X", "Tensor of shape [a_1, a_2, ..., a_n, r]", "T") .Input( 1, "K", "A 1-D tensor containing a single positive value corresponding to the number of top elements to retrieve", "tensor(int64)") .Output( 0, "Values", "Tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] " "containing top K values from the input tensor", "T") .Output( 1, "Indices", "Tensor of shape [a_1, a_2, ..., a_{axis-1}, k, a_{axis+1}, ... a_n] " "containing the corresponding input tensor indices for the top K " "values.", "I") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to numeric tensors.") .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64") .Attr( "axis", "Dimension on which to do the sort. Negative value means counting dimensions " "from the back. Accepted range is [-r, r-1] where r = rank(input).", AttributeProto::INT, static_cast(-1)) .Attr( "largest", "Whether to return the top-K largest or smallest elements.", AttributeProto::INT, static_cast(1)) .Attr( "sorted", "Whether to return the elements in sorted order.", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference: propagateElemTypeFromInputToOutput(ctx, 0, 0); updateOutputElemType(ctx, 1, TensorProto::INT64); // Shape inference: if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); int64_t rank = input_shape.dim_size(); int64_t axis = getAttribute(ctx, "axis", -1); if (axis < 0) axis += rank; if (axis < 0 || axis >= rank) fail_shape_inference("Invalid value for attribute axis"); const auto& axis_dim = input_shape.dim(static_cast(axis)); const auto* k = ctx.getInputData(1); // Infer output shape if: // (1) 'K' is available // (2) axis_dim has dim value // Othewise cannot reliably compute output shape as axis dim value is // unknown and hence cannot determine if axis dim value >= k (which // should be enforced) if (nullptr != k && axis_dim.has_dim_value()) { int64_t k_value = 0; if (k->dims_size() != 1 || k->dims(0) != 1) fail_shape_inference( "K input must be a one-dimensional tensor of size 1."); if (k->data_type() == TensorProto::INT64) { const auto& data = ParseData(k); k_value = data[0]; } else fail_shape_inference("K input must be of type int64."); if (axis_dim.dim_value() < k_value) fail_shape_inference( "Axis has less than the requested k elements."); TensorShapeProto result_shape = input_shape; result_shape.mutable_dim(static_cast(axis)) ->set_dim_value(k_value); updateOutputShape(ctx, 0, result_shape); updateOutputShape(ctx, 1, result_shape); return; } // Infer output shapes' rank in any case auto* output_shape_0 = getOutputShape(ctx, 0); auto* output_shape_1 = getOutputShape(ctx, 1); for (int i = 0; i < input_shape.dim_size(); ++i) { output_shape_0->add_dim(); output_shape_1->add_dim(); } return; })); static const char* Sin_ver7_doc = R"DOC( Calculates the sine of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sin, 7, OpSchema() .SetDoc(Sin_ver7_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The sine of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Cos_ver7_doc = R"DOC( Calculates the cosine of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Cos, 7, OpSchema() .SetDoc(Cos_ver7_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The cosine of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Tan_ver7_doc = R"DOC( Calculates the tangent of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Tan, 7, OpSchema() .SetDoc(Tan_ver7_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The tangent of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Asin_ver7_doc = R"DOC( Calculates the arcsine (inverse of sine) of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Asin, 7, OpSchema() .SetDoc(Asin_ver7_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The arcsine of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Acos_ver7_doc = R"DOC( Calculates the arccosine (inverse of cosine) of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Acos, 7, OpSchema() .SetDoc(Acos_ver7_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The arccosine of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Atan_ver7_doc = R"DOC( Calculates the arctangent (inverse of tangent) of the given input tensor, element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Atan, 7, OpSchema() .SetDoc(Atan_ver7_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The arctangent of the input tensor computed " "element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Expand_ver8_doc = R"DOC( Broadcast the input tensor following the given shape and the broadcast rule. The broadcast rule is similar to numpy.array(input) * numpy.ones(shape): Dimensions are right alignment; Two corresponding dimension must have the same value, or one of them is equal to 1. Also, this operator is similar to numpy.broadcast_to(input, shape), but the major difference is numpy.broadcast_to() does not allow shape to be smaller than input.size(). It is possible that the output.shape is not equal to shape, when some dimensions in shape is equal to 1, or the shape.ndim < input.shape.ndim. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Expand, 8, OpSchema() .SetDoc(Expand_ver8_doc) .Input(0, "input", "Input tensor", "T") .Input( 1, "shape", "A 1-D tensor indicates the shape you want to expand to, following the broadcast rule", "tensor(int64)") .Output(0, "output", "Output tensor", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference // For shape inference (and rank inference), we need both input shape // and values in 'shape' tensor const auto* shape_initializer = ctx.getInputData(1); if (hasNInputShapes(ctx, 1) && nullptr != shape_initializer) { const auto& shape_initializer_shape = ctx.getInputType(1)->tensor_type().shape(); if (shape_initializer_shape.dim_size() != 1 || shape_initializer->data_type() != TensorProto::INT64) fail_shape_inference( "'shape' input must be 1D tensor of type INT64"); const auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const auto& shape_data = ParseData(shape_initializer); TensorShapeProto second_shape; for (const auto& e : shape_data) { auto* dim = second_shape.add_dim(); dim->set_dim_value(e); } bidirectionalBroadcastShapeInference( input_shape, second_shape, *getOutputShape(ctx, 0)); } return; })); static const char* Sinh_ver9_doc = R"DOC( Calculates the hyperbolic sine of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sinh, 9, OpSchema() .SetDoc(Sinh_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The hyperbolic sine values of the input tensor " "computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Cosh_ver9_doc = R"DOC( Calculates the hyperbolic cosine of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Cosh, 9, OpSchema() .SetDoc(Cosh_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The hyperbolic cosine values of the input tensor " "computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Asinh_ver9_doc = R"DOC( Calculates the hyperbolic arcsine of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Asinh, 9, OpSchema() .SetDoc(Asinh_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The hyperbolic arcsine values of the input tensor " "computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Acosh_ver9_doc = R"DOC( Calculates the hyperbolic arccosine of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Acosh, 9, OpSchema() .SetDoc(Acosh_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The hyperbolic arccosine values of the input tensor " "computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Atanh_ver9_doc = R"DOC( Calculates the hyperbolic arctangent of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Atanh, 9, OpSchema() .SetDoc(Atanh_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The hyperbolic arctangent values of the input tensor " "computed element-wise", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Sign_ver9_doc = R"DOC( Calculate the sign of the given input tensor element-wise. If input > 0, output 1. if input < 0, output -1. if input == 0, output 0. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Sign, 9, OpSchema() .SetDoc(Sign_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The sign of the input tensor " "computed element-wise. It has the same shape and type of the input.", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Erf_ver9_doc = R"DOC( Computes the error function of the given input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Erf, 9, OpSchema() .SetDoc(Erf_ver9_doc) .Input(0, "input", "Input tensor", "T") .Output( 0, "output", "The error function of the input tensor " "computed element-wise. It has the same shape and type of the input.", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numeric tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* QLinearMatMul_ver10_doc = R"DOC( Matrix product that behaves like numpy.matmul: https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.matmul.html. It consumes two quantized input tensors, their scales and zero points, scale and zero point of output, and computes the quantized output. The quantization formula is y = saturate((x / y_scale) + y_zero_point). For (x / y_scale), it is rounding to nearest ties to even. Refer to https://en.wikipedia.org/wiki/Rounding for details. Scale and zero point must have same shape. They must be either scalar (per tensor) or 1-D tensor (per row for 'a' and per column for 'b'). If scale and zero point are 1-D tensor, the number of elements of scale and zero point tensor of input 'a' and output 'y' should be equal to the number of rows of input 'a', and the number of elements of scale and zero point tensor of input 'b' should be equal to the number of columns of input 'b'. Production must never overflow, and accumulation may overflow if and only if in 32 bits. )DOC"; ONNX_OPERATOR_SET_SCHEMA( QLinearMatMul, 10, OpSchema() .SetDoc(QLinearMatMul_ver10_doc) .Input(0, "a", "N-dimensional quantized matrix a", "T1") .Input(1, "a_scale", "scale of quantized input a", "tensor(float)") .Input(2, "a_zero_point", "zero point of quantized input a", "T1") .Input(3, "b", "N-dimensional quantized matrix b", "T2") .Input(4, "b_scale", "scale of quantized input b", "tensor(float)") .Input(5, "b_zero_point", "zero point of quantized input b", "T2") .Input(6, "y_scale", "scale of quantized output y", "tensor(float)") .Input(7, "y_zero_point", "zero point of quantized output y", "T3") .Output(0, "y", "Quantized matrix multiply results from a * b", "T3") .TypeConstraint( "T1", {"tensor(int8)", "tensor(uint8)"}, "Constrain input a and its zero point data type to 8-bit integer tensor.") .TypeConstraint( "T2", {"tensor(int8)", "tensor(uint8)"}, "Constrain input b and its zero point data type to 8-bit integer tensor.") .TypeConstraint( "T3", {"tensor(int8)", "tensor(uint8)"}, "Constrain output y and its zero point data type to 8-bit integer tensor.") .TypeAndShapeInferenceFunction([](ONNX_NAMESPACE::InferenceContext& ctx) { auto a_type = ctx.getInputType(0); auto b_type = ctx.getInputType(3); if (nullptr == a_type || nullptr == b_type || a_type->value_case() != ONNX_NAMESPACE::TypeProto::kTensorType || b_type->value_case() != ONNX_NAMESPACE::TypeProto::kTensorType) { fail_type_inference("inputs are expected to have tensor type."); } auto a_zero_point_type = ctx.getInputType(2); if (nullptr == a_zero_point_type || a_zero_point_type->tensor_type().elem_type() != a_type->tensor_type().elem_type()) { fail_type_inference( "input and zero_point pair is expected to have be same type."); } auto b_zero_point_type = ctx.getInputType(5); if (nullptr == b_zero_point_type || b_zero_point_type->tensor_type().elem_type() != b_type->tensor_type().elem_type()) { fail_type_inference( "input and zero_point pair is expected to have same type."); } propagateElemTypeFromInputToOutput(ctx, 7, 0); matmulShapeInference(ctx, 0, 3); })); static const char* MatMulInteger_ver10_doc = R"DOC( Matrix product that behaves like numpy.matmul: https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.matmul.html. The production MUST never overflow. The accumulation may overflow if and only if in 32 bits. )DOC"; ONNX_OPERATOR_SET_SCHEMA( MatMulInteger, 10, OpSchema() .SetDoc(MatMulInteger_ver10_doc) .Input(0, "A", "N-dimensional matrix A", "T1") .Input(1, "B", "N-dimensional matrix B", "T2") .Input( 2, "a_zero_point", "Zero point tensor for input 'A'. It's optional and default value is 0. It could be a scalar or a 1-D tensor, " "which means a per-tensor or per-row quantization. If it's a 1-D tensor, its number of elements " "should be equal to the number of rows of input 'A'.", "T1", OpSchema::Optional) .Input( 3, "b_zero_point", "Zero point tensor for input 'B'. It's optional and default value is 0. It could be a scalar or a 1-D tensor, " "which means a per-tensor or per-column quantization. If it's a 1-D tensor, its number " "of elements should be equal to the number of columns of input 'B'.", "T2", OpSchema::Optional) .Output(0, "Y", "Matrix multiply results from A * B", "T3") .TypeConstraint( "T1", {"tensor(int8)", "tensor(uint8)"}, "Constrain input A data type to 8-bit integer tensor.") .TypeConstraint( "T2", {"tensor(int8)", "tensor(uint8)"}, "Constrain input B data type to 8-bit integer tensor.") .TypeConstraint( "T3", {"tensor(int32)"}, "Constrain output Y data type as 32-bit integer tensor.") .TypeAndShapeInferenceFunction([](ONNX_NAMESPACE::InferenceContext& ctx) { auto a_type = ctx.getInputType(0); auto b_type = ctx.getInputType(1); auto y_type = ctx.getOutputType(0); if (nullptr == a_type || nullptr == b_type || nullptr == y_type || a_type->value_case() != ONNX_NAMESPACE::TypeProto::kTensorType || b_type->value_case() != ONNX_NAMESPACE::TypeProto::kTensorType) { fail_type_inference( "inputs are expected to have tensor type and output type should not be null."); } // Right now we only support int32 y_type->mutable_tensor_type()->set_elem_type( ONNX_NAMESPACE::TensorProto::INT32); matmulShapeInference(ctx, 0, 1); })); static const char* CumSum_ver11_doc = R"DOC( Performs cumulative sum of the input elements along the given axis. By default, it will do the sum inclusively meaning the first element is copied as is. Through an `exclusive` attribute, this behavior can change to exclude the first element. It can also perform summation in the opposite direction of the axis. For that, set `reverse` attribute to 1. Example: ``` input_x = [1, 2, 3] axis=0 output = [1, 3, 6] exclusive=1 output = [0, 1, 3] exclusive=0 reverse=1 output = [6, 5, 3] exclusive=1 reverse=1 output = [5, 3, 0] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( CumSum, 11, OpSchema() .SetDoc(CumSum_ver11_doc) .Attr( "exclusive", "If set to 1 will return exclusive sum in which the top element is not included." " In other terms, if set to 1, the j-th output element would be the sum of the first (j-1) elements." " Otherwise, it would be the sum of the first j elements.", AttributeProto::INT, static_cast(0)) .Attr( "reverse", "If set to 1 will perform the sums in reverse direction.", AttributeProto::INT, static_cast(0)) .Input(0, "x", "An input tensor that is to be processed.", "T") .Input( 1, "axis", "(Optional) A 0-D tensor. Must be in the range [-rank(x), rank(x)-1]. " "Negative value means counting dimensions from the back.", "T2") .Output( 0, "y", "Output tensor of the same type as 'x' with cumulative sums of the x's elements", "T") .TypeConstraint( "T", {"tensor(uint32)", "tensor(uint64)", "tensor(int32)", "tensor(int64)", "tensor(float)", "tensor(double)"}, "Input can be of any tensor type.") .TypeConstraint( "T2", {"tensor(int32)", "tensor(int64)"}, "axis tensor can be int32 or int64 only") .TypeAndShapeInferenceFunction( ONNX_NAMESPACE::propagateShapeAndTypeFromFirstInput)); static const char* Round_ver11_doc = R"DOC( Round takes one input Tensor and rounds the values, element-wise, meaning it finds the nearest integer for each value. In case of halfs, the rule is to round them to the nearest even integer. The output tensor has the same shape and type as the input. Examples: ``` round([0.9]) = [1.0] round([2.5]) = [2.0] round([2.3]) = [2.0] round([1.5]) = [2.0] round([-4.5]) = [-4.0] ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( Round, 11, OpSchema() .SetDoc(Round_ver11_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Det_ver11_doc = R"DOC( Det calculates determinant of a square matrix or batches of square matrices. Det takes one input tensor of shape `[*, M, M]`, where `*` is zero or more batch dimensions, and the inner-most 2 dimensions form square matrices. The output is a tensor of shape `[*]`, containing the determinants of all input submatrices. e.g., When the input is 2-D, the output is a scalar(shape is empty: `[]`). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Det, 11, OpSchema() .SetDoc(Det_ver11_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to floating-point tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference if (hasInputShape(ctx, 0)) { const TensorShapeProto& input_shape = ctx.getInputType(0)->tensor_type().shape(); TensorShapeProto* output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); const int rank = static_cast(input_shape.dim_size()); if (rank < 2) { fail_shape_inference("Input rank must be >= 2.") } const auto mat_w = input_shape.dim(rank - 1); const auto mat_h = input_shape.dim(rank - 2); if (mat_w.has_dim_value() && mat_h.has_dim_value() && (mat_w.dim_value() != mat_h.dim_value())) { fail_shape_inference( "The inner-most 2 dimensions must have the same size (mat_w:", mat_w.dim_value(), " != mat_h:", mat_h.dim_value(), ")."); } for (int i = 0; i < rank - 2; ++i) { auto* dim = output_shape->add_dim(); *dim = input_shape.dim(i); } } })); static const char* NegativeLogLikelihoodLoss_ver12_doc = R"DOC( A NegativeLogLikelihoodLoss operator computes (weighted) negative log likelihood loss. Its "input" tensor has the shape of (N, C, d1, d2, ..., dk) where k >= 0. The "input" tensor contains log-probabilities for input[n, :, d_1, d_2,..., d_k] being in a class of [0, C). The operator's "target" input tensor has the shape of (N, d1, d2, ..., dk). It encodes class labels (one of C classes) or it may contain a special value (indicated by an attribute ignore_index) for N x d1 x d2 x ... x dk samples. The loss value for input[n, :, d_1, d_2,...d_k] being classified as class c = target[n][d_1][d_2]...[d_k] is computed as: loss[n][d_1][d_2]...[d_k] = -input[n][c][d_1][d_2]...[d_k]. When an optional "weight" is provided, the sample loss is calculated as: loss[n][d_1][d_2]...[d_k] = -input[n][c][d_1][d_2]...[d_k] * weight[c]. loss is zero for the case when target-value equals ignore_index. loss[n][d_1][d_2]...[d_k] = 0, when target[n][d_1][d_2]...[d_k] = ignore_index If "reduction" attribute is set to "none", the operator's output will be the above loss with shape (N, d1, d2, ..., dk). If "reduction" attribute is set to "mean" (the default attribute value), the output loss is (weight) averaged: mean(loss), if "weight" is not provided, or if weight is provided, sum(loss) / sum(weight[target[n][d_1][d_2]...[d_k]]]), for all samples. If "reduction" attribute is set to "sum", the output is a scalar: sum(loss). See also https://pytorch.org/docs/stable/nn.html#torch.nn.NLLLoss. Example 1: // negative log likelihood loss, "none" reduction N, C, d1 = 2, 3, 2 input = [[[1.0, 2.0], [2.0, 2.0], [3.0, 2.0]], [[0.0, 1.0], [2.0, 2.0], [1.0, 2]]] target = [[2, 1], [0, 2]] loss = np.zeros((N, d1)) for n in range(N): for d_1 in range(d1): c = target[n][d_1] loss[n][d_1] = -input[n][c][d_1] // print(loss) // [[-3. -2.] // [-0. -2.]] Example 2: // weighted negative log likelihood loss, sum reduction N, C, d1 = 2, 3, 2 input = [[[1.0, 2.0], [2.0, 2.0], [3.0, 2.0]], [[0.0, 1.0], [2.0, 2.0], [1.0, 2]]] target = [[2, 1], [0, 2]] weight = [0.2, 0.3, 0.1] loss = np.zeros((N, d1)) for n in range(N): for d_1 in range(d1): c = target[n][d_1] loss[n][d_1] = -input[n][c][d_1] * weight[c] loss = np.sum(loss) // print(loss) // -1.1 Example 3: // weighted negative log likelihood loss, mean reduction N, C, d1 = 2, 3, 2 input = [[[1.0, 2.0], [2.0, 2.0], [3.0, 2.0]], [[0.0, 1.0], [2.0, 2.0], [1.0, 2]]] target = [[2, 1], [0, 2]] weight = [0.2, 0.3, 0.1] loss = np.zeros((N, d1)) weight_total = 0 for n in range(N): for d_1 in range(d1): c = target[n][d_1] loss[n][d_1] = -input[n][c][d_1] * weight[c] weight_total = weight_total + weight[c] loss = np.sum(loss) / weight_total // print(loss) // -1.57 )DOC"; TensorProto ToDimensionOneTensor(int32_t value) { auto t = ToTensor(std::vector({value})); t.add_dims(1); return t; } TensorProto ToDimensionOneInt64Tensor(int64_t value) { auto t = ToTensor(std::vector({value})); t.add_dims(1); return t; } bool BuildContextDependentFunctionBody( const FunctionBodyBuildContext& ctx, const OpSchema& schema, FunctionProto& functionProto) { std::vector body; body.push_back({{"const_zero"}, "Constant", {}, {MakeAttribute("value", ToDimensionOneTensor(0))}}); body.push_back({{"const_one"}, "Constant", {}, {MakeAttribute("value", ToDimensionOneTensor(1))}}); body.push_back({{"expanded_target"}, "Unsqueeze", {"target"}, {MakeAttribute("axes", std::vector({1}))}}); if (ctx.getAttribute("ignore_index") == nullptr) { body.push_back({{"input_gather_element"}, "GatherElements", {"input", "expanded_target"}, {MakeAttribute("axis", (int64_t)1)}}); body.push_back({{"loss_NCdd"}, "Neg", {"input_gather_element"}}); body.push_back({{"loss_N1dd"}, "Slice", {"loss_NCdd", "const_zero", "const_one", "const_one"}}); if (!ctx.hasInput(2)) { if (ctx.getAttribute("reduction")->s() == "none") { body.push_back({{"loss"}, "Squeeze", {"loss_N1dd"}, {MakeAttribute("axes", std::vector({1}))}}); } else { body.push_back({{"loss_Ndd"}, "Squeeze", {"loss_N1dd"}, {MakeAttribute("axes", std::vector({1}))}}); if (ctx.getAttribute("reduction")->s() == "mean") { body.push_back({{"loss"}, "ReduceMean", {"loss_Ndd"}, {MakeAttribute("keepdims", (int64_t)0)}}); } else { body.push_back({{"loss"}, "ReduceSum", {"loss_Ndd"}, {MakeAttribute("keepdims", (int64_t)0)}}); } } } else { body.push_back({{"weight_gather"}, "Gather", {"weight", "target"}}); body.push_back({{"loss_unweighted"}, "Squeeze", {"loss_N1dd"}, {MakeAttribute("axes", std::vector({1}))}}); if (ctx.getAttribute("reduction")->s() == "none") { body.push_back({{"loss"}, "Mul", {"loss_unweighted", "weight_gather"}}); } else { body.push_back( {{"loss_Ndd"}, "Mul", {"loss_unweighted", "weight_gather"}}); if (ctx.getAttribute("reduction")->s() == "mean") { body.push_back({{"loss_sum"}, "ReduceSum", {"loss_Ndd"}, {MakeAttribute("keepdims", (int64_t)0)}}); body.push_back({{"weight_gather_sum"}, "ReduceSum", {"weight_gather"}, {MakeAttribute("keepdims", (int64_t)0)}}); body.push_back({{"loss"}, "Div", {"loss_sum", "weight_gather_sum"}}); } else { body.push_back({{"loss"}, "ReduceSum", {"loss_Ndd"}, {MakeAttribute("keepdims", (int64_t)0)}}); } } } } else { body.push_back( {{"const_ignore_index"}, "Constant", {}, {MakeAttribute( "value", ToDimensionOneInt64Tensor(ctx.getAttribute("ignore_index")->i()))}}); body.push_back({{"const_zero_target_typed"}, "Sub", {"expanded_target", "expanded_target"}}); body.push_back({{"expanded_target_int64"}, "Cast", {"expanded_target"}, {MakeAttribute("to", (int64_t)TensorProto_DataType::TensorProto_DataType_INT64)}}); body.push_back({{"mask"}, "Equal", {"expanded_target_int64", "const_ignore_index"}}); body.push_back({{"transform_targets"}, "Where", {"mask", "const_zero_target_typed", "expanded_target"}}); body.push_back({{"input_gather_element"}, "GatherElements", {"input", "transform_targets"}, {MakeAttribute("axis", (int64_t)1)}}); body.push_back({{"const_zero_float"}, "Constant", {}, {MakeAttribute("value", ToDimensionOneFloatTensor(0.0f))}}); body.push_back( {{"input_gather_element_transform"}, "Where", {"mask", "const_zero_float", "input_gather_element"}}); body.push_back({{"loss_NCdd"}, "Neg", {"input_gather_element_transform"}}); body.push_back({{"loss_N1dd"}, "Slice", {"loss_NCdd", "const_zero", "const_one", "const_one"}}); if (!ctx.hasInput(2)) { body.push_back({{"squeeze_mask"}, "Squeeze", {"mask"}, {MakeAttribute("axes", std::vector({1}))}}); body.push_back({{"const_one_float"}, "Constant", {}, {MakeAttribute("value", ToDimensionOneFloatTensor(1.0f))}}); body.push_back({{"weight_gather"}, "Where", {"squeeze_mask", "const_zero_float", "const_one_float"}}); } else { body.push_back( {{"weight_gather_temp"}, "Gather", {"weight", "transform_targets"}}); body.push_back( {{"weight_gather_temp_1"}, "Where", {"mask", "const_zero_float", "weight_gather_temp"}}); body.push_back({{"weight_gather"}, "Squeeze", {"weight_gather_temp_1"}, {MakeAttribute("axes", std::vector({1}))}}); } body.push_back({{"loss_unweighted"}, "Squeeze", {"loss_N1dd"}, {MakeAttribute("axes", std::vector({1}))}}); if (ctx.getAttribute("reduction")->s() == "none") { body.push_back({{"loss"}, "Mul", {"loss_unweighted", "weight_gather"}}); } else { body.push_back( {{"loss_Ndd"}, "Mul", {"loss_unweighted", "weight_gather"}}); if (ctx.getAttribute("reduction")->s() == "mean") { body.push_back({{"loss_sum"}, "ReduceSum", {"loss_Ndd"}, {MakeAttribute("keepdims", (int64_t)0)}}); body.push_back({{"weight_gather_sum"}, "ReduceSum", {"weight_gather"}, {MakeAttribute("keepdims", (int64_t)0)}}); body.push_back({{"loss"}, "Div", {"loss_sum", "weight_gather_sum"}}); } else { body.push_back({{"loss"}, "ReduceSum", {"loss_Ndd"}, {MakeAttribute("keepdims", (int64_t)0)}}); } } } auto func_nodes = FunctionBodyHelper::BuildNodes(body); for (const auto& node : func_nodes) { auto new_node = functionProto.add_node(); new_node->CopyFrom(node); } schema.BuildFunction(functionProto); return true; } ONNX_OPERATOR_SET_SCHEMA( NegativeLogLikelihoodLoss, 12, OpSchema() .SetDoc(NegativeLogLikelihoodLoss_ver12_doc) .Input( 0, "input", "Input tensor of shape (N, C) or (N, C, d1, d2, ..., dk).", "T") .Input( 1, "target", "Target tensor of shape (N) or (N, d1, d2, ..., dk). Target element value shall be in range of [0, C). " "If ignore_index is specified, it may have a value outside [0, C) and the target values should either be " "in the range [0, C) or have the value ignore_index.", "Tind") .Input( 2, "weight", "Optional rescaling weight tensor. " "If given, it has to be a tensor of size C. Otherwise, it is treated as if having all ones.", "T", OpSchema::Optional) .Output(0, "loss", "The negative log likelihood loss", "T") .Attr( "reduction", "Type of reduction to apply to loss: none, sum, mean (default). " "'none': the output is the loss for each sample. " "'sum': the output will be summed. " "'mean': the sum of the output will be divided by the sum of applied weights.", AttributeProto::STRING, std::string("mean")) .Attr( "ignore_index", "Specifies a target value that is ignored and does not contribute to the input gradient. It's an optional value.", AttributeProto::INT, false) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input, weight, and output types to floating-point tensors.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain target to integer types") .SetContextDependentFunctionBodyBuilder( BuildContextDependentFunctionBody) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference if (hasInputShape(ctx, 0) && hasInputShape(ctx, 1)) { const TensorShapeProto& input_shape = ctx.getInputType(0)->tensor_type().shape(); const TensorShapeProto& target_shape = ctx.getInputType(1)->tensor_type().shape(); const int input_rank = static_cast(input_shape.dim_size()); const int target_rank = static_cast(target_shape.dim_size()); if (input_rank < 2) { fail_shape_inference("Input rank must be >= 2.") } if (target_rank != input_rank - 1) { fail_shape_inference( "Target rank must be 1 less than the input rank.") } // match input dimensions (N, C, d1, ..., dk) with target // dimensions of (C, d1, ..., dk) for (int dim = 0; dim < target_rank; dim++) { const auto input_dim = dim == 0 ? input_shape.dim(dim) : input_shape.dim(dim + 1); const auto target_dim = target_shape.dim(dim); if (input_dim.has_dim_value() && target_dim.has_dim_value() && input_dim.dim_value() != target_dim.dim_value()) fail_shape_inference( "Input and target dimension value mismatch.") } if (ctx.getNumInputs() == 3) { const TensorShapeProto& weight_shape = ctx.getInputType(2)->tensor_type().shape(); if (weight_shape.dim_size() != 1) fail_shape_inference("Weight rank must be 1.") } TensorShapeProto* output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); if (ctx.getAttribute("reduction")->s() == "none") { // output tensor is of shape (N, d1, d2, ..., dk) if // reduction attribute is "none". for (int i = 0; i < input_rank - 1; i++) { auto* dim = output_shape->add_dim(); if (i == 0) *dim = input_shape.dim(i); else *dim = input_shape.dim(i + 1); } } // otherwise output is a scalar. } })); void einsumRankInference( ONNX_NAMESPACE::InferenceContext& ctx, std::string equation) { const size_t numInputs = ctx.getNumInputs(); if (numInputs < 1 || !hasNInputShapes(ctx, static_cast(numInputs))) { return; } auto* output_shape = getOutputShape(ctx, 0); std::string left_equation; equation.erase( std::remove(equation.begin(), equation.end(), ' '), equation.end()); // Remove space char auto mid_index = equation.find("->"); if (mid_index != std::string::npos) { // Separate right and left hand sides of the equation left_equation = equation.substr(0, mid_index); } else { // No right hand side left_equation = equation; } std::string term; size_t num_operands = 0; size_t num_ellipsis = 0; size_t num_ellipsis_indices = 0; // Parse the left-hand side std::stringstream str(left_equation); while (!str.eof()) { std::getline(str, term, ','); auto ellipsis_index = term.find("..."); if (numInputs <= num_operands) { fail_shape_inference( "Number of input tensors does not match the operands in the equation."); } size_t rank = ctx.getInputType(num_operands)->tensor_type().shape().dim_size(); if (ellipsis_index != std::string::npos) { // If there is an ellipsis, the number of dimensions it represents // must be total dim - letter dimensions if (num_ellipsis == 0) { if (rank + 3 < term.size()) { fail_shape_inference("Ellipsis represents incompatible dimensions."); } num_ellipsis_indices = rank - term.size() + 3; } else { // ellipsis has been seen before. Check that if dimensions // are compatible if (num_ellipsis_indices != rank - term.size() + 3) { fail_shape_inference("Ellipsis represents incompatible dimensions."); } } num_ellipsis++; } else { if (rank != term.size()) { fail_shape_inference("Rank of input ", num_operands, " does not match the equation indices."); } } num_operands++; } if (numInputs != num_operands) { fail_shape_inference( "Number of input tensors does not match the operands in the equation."); } const size_t number_of_letters = 26; size_t num_letter_occurrences[number_of_letters] = {0}; // Parse the provided right-hand side if (mid_index != std::string::npos) { std::string right_equation = equation.substr(mid_index + 2); auto right_ellipsis_index = right_equation.find("..."); if (right_ellipsis_index != std::string::npos) { // Right-hand side contains ellipsis for (size_t i = 0; i < num_ellipsis_indices; ++i) { output_shape->add_dim(); } } for (char c : right_equation) { // Add a dimension per each character // in right hand equation if (c != '.') { output_shape->add_dim(); } } } else { // Infer the dimension for right-hand side // If there's an ellipsis, add it's corresponding dimensions for (size_t i = 0; i < num_ellipsis_indices; i++) { output_shape->add_dim(); } for (size_t i = 0; i < left_equation.size(); i++) { // Count chars that appear exactly once on left hand side if ((left_equation.at(i) != ',') && (left_equation.at(i) != '.')) { num_letter_occurrences[left_equation.at(i) - 'a']++; } } for (size_t index = 0; index < number_of_letters; index++) { if (num_letter_occurrences[index] == 1) { output_shape->add_dim(); } } } } static const char* Einsum_ver12_doc = R"DOC( An einsum of the form ```term1, term2 -> output-term``` produces an output tensor using the following equation ```output[output-term] = reduce-sum( input1[term1] * input2[term] )``` where the reduce-sum performs a summation over all the indices occurring in in the input terms (term1, term2) that do not occur in the output-term. The Einsum operator evaluates algebraic tensor operations on a sequence of tensors, using the Einstein summation convention. The equation string contains a comma-separated sequence of lower case letters. Each term corresponds to an operand tensor, and the characters within the terms correspond to operands dimensions. This sequence may be followed by "->" to separate the left and right hand side of the equation. If the equation contains "->" followed by the right-hand side, the explicit (not classical) form of the Einstein summation is performed, and the right-hand side indices indicate output tensor dimensions. In other cases, output indices are (implicitly) set to the alphabetically sorted sequence of indices appearing exactly once in the equation. When a dimension character is repeated in the left-hand side, it represents summation along the dimension. The equation may contain ellipsis ("...") to enable broadcasting. Ellipsis must indicate a fixed number of dimensions. Specifically, every occurrence of ellipsis in the equation must represent the same number of dimensions. The right-hand side may contain exactly one ellipsis. In implicit mode, the ellipsis dimensions are set to the beginning of the output. The equation string may contain space (U+0020) character. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Einsum, 12, OpSchema() .SetDoc(Einsum_ver12_doc) .Attr("equation", "Einsum expression string.", AttributeProto::STRING) .Input(0, "Inputs", "Operands", "T", OpSchema::Variadic) .Output(0, "Output", "Output tensor", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrain input and output types to all numerical tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); std::string equation = getAttribute(ctx, "equation", ""); if (equation.compare("") == 0) { return; } einsumRankInference(ctx, equation); })); const char* reduction_doc_sce = "Type of reduction to apply to loss: none, sum, mean(default). " "'none': no reduction will be applied, " "'sum': the output will be summed. " "'mean': the sum of the output will be divided by the number of " "elements in the output."; static const char* SoftmaxCrossEntropyLoss_ver12_doc = R"DOC(Loss function that measures the softmax cross entropy between 'scores' and 'labels'. This operator first computes a loss tensor whose shape is identical to the labels input. If the input is 2-D with shape (N, C), the loss tensor may be a N-element vector L = (l_1, l_2, ..., l_N). If the input is N-D tensor with shape (N, C, D1, D2, ..., Dk), the loss tensor L may have (N, D1, D2, ..., Dk) as its shape and L[i,][j_1][j_2]...[j_k] denotes a scalar element in L. After L is available, this operator can optionally do a reduction operator. shape(scores): (N, C) where C is the number of classes, or (N, C, D1, D2,..., Dk), with K >= 1 in case of K-dimensional loss. shape(labels): (N) where each value is 0 <= labels[i] <= C-1, or (N, D1, D2,..., Dk), with K >= 1 in case of K-dimensional loss. The loss for one sample, l_i, can caculated as follows: l[i][d1][d2]...[dk] = -y[i][c][d1][d2]..[dk], where i is the index of classes. or l[i][d1][d2]...[dk] = -y[i][c][d1][d2]..[dk] * weights[c], if 'weights' is provided. loss is zero for the case when label-value equals ignore_index. l[i][d1][d2]...[dk] = 0, when labels[n][d1][d2]...[dk] = ignore_index where: p = Softmax(scores) y = Log(p) c = labels[i][d1][d2]...[dk] Finally, L is optionally reduced: If reduction = 'none', the output is L with shape (N, D1, D2, ..., Dk). If reduction = 'sum', the output is scalar: Sum(L). If reduction = 'mean', the output is scalar: ReduceMean(L), or if weight is provided: ReduceSum(L) / ReduceSum(W), where tensor W is of shape (N, D1, D2, ..., Dk) and W[n][d1][d2]...[dk] = weights[labels[i][d1][d2]...[dk]]. )DOC"; bool BuildContextDependentFunctionBodySCE( const FunctionBodyBuildContext& ctx, const OpSchema& schema, FunctionProto& functionProto) { std::vector body; body.push_back({{"X_Max"}, "ReduceMax", {"scores"}, {MakeAttribute("axes", std::vector({1}))}}); body.push_back({{"X_Sub"}, "Sub", {"scores", "X_Max"}}); body.push_back({{"X_Exp"}, "Exp", {"X_Sub"}}); body.push_back({{"X_RS"}, "ReduceSum", {"X_Exp"}, {MakeAttribute("axes", std::vector({1}))}}); body.push_back({{"X_Div"}, "Div", {"X_Exp", "X_RS"}}); body.push_back({{"X_Log"}, "Log", {"X_Div"}}); // Review(mzs): Ideally we want to reuse the output from Log for sub-graph output as well but // looking at the graph resolve code it does not include graph outputs as intermediate outputs, hence // if intermediate X_log is renamed as log_prob then it will be treated as graph output and will not // be available to NegativeLogLikelihoodLoss. May be my understanding is incorrect or there is a bug in // function population code in ORTbut I will dig further to be 100%. // In the meantime we just replicate the log. if(ctx.hasOutput(1)){ body.push_back({{"log_prob"}, "Identity", {"X_Log"}}); } std::vector input_tensor_names{"X_Log", "labels"}; std::vector attributes{MakeRefAttribute("reduction", AttributeProto::STRING)}; // Add weights as input if needed. if(ctx.hasInput(2)){ input_tensor_names.push_back("weights"); } // add ignore_index attributes if needed. if (ctx.getAttribute("ignore_index") != nullptr){ attributes.push_back(MakeRefAttribute("ignore_index", AttributeProto::INT)); } body.push_back({{"output"}, "NegativeLogLikelihoodLoss", input_tensor_names, attributes}); auto func_nodes = FunctionBodyHelper::BuildNodes(body); for (const auto& node : func_nodes) { auto new_node = functionProto.add_node(); new_node->CopyFrom(node); } schema.BuildFunction(functionProto); return true; } ONNX_OPERATOR_SET_SCHEMA( SoftmaxCrossEntropyLoss, 12, OpSchema() .SetDoc(SoftmaxCrossEntropyLoss_ver12_doc) .Attr( "reduction", reduction_doc_sce, AttributeProto::STRING, std::string("mean")) .Attr( "ignore_index", "Specifies a target value that is ignored and does not contribute to the input gradient. It's an optional value.", AttributeProto::INT, false) .Input( 0, "scores", "The predicted outputs with shape [batch_size, class_size], or " "[batch_size, class_size, D1, D2 , ..., Dk], where K is the number of dimensions.", "T") .Input( 1, "labels", "The ground truth output tensor, with shape [batch_size], or " "[batch_size, D1, D2, ..., Dk], where K is the number of dimensions. " "Labels element value shall be in range of [0, C). " "If ignore_index is specified, it may have a value outside [0, C) and the label values should either be " "in the range [0, C) or have the value ignore_index.", "Tind") .Input( 2, "weights", "A manual rescaling weight given to each class. If given, it has to " "be a 1D Tensor assigning weight to each of the classes. Otherwise, " "it is treated as if having all ones.", "T", OpSchema::Optional) .Output( 0, "output", "Weighted loss float Tensor. If reduction is 'none', this has the " "shape of [batch_size], or [batch_size, D1, D2, ..., Dk] in case of " "K-dimensional loss. Otherwise, it is a scalar.", "T") .Output( 1, "log_prob", "Log probability tensor. If the output of softmax is prob, its value is log(prob).", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "Tind", {"tensor(int32)", "tensor(int64)"}, "Constrain target to integer types") .SetContextDependentFunctionBodyBuilder( BuildContextDependentFunctionBodySCE) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); std::string reduction = getAttribute(ctx, "reduction", "mean"); if (reduction.compare("none") == 0) { if (hasInputShape(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 1, 0); } } else { updateOutputShape(ctx, 0, TensorShapeProto()); } if(ctx.getNumOutputs() == 2) { propagateElemTypeFromInputToOutput(ctx, 0, 1); propagateShapeFromInputToOutput(ctx, 0, 1); } })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/data_type_utils.h0000664000000000000000000000361713655345213016465 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #ifndef ONNX_DATA_TYPE_UTILS_H #define ONNX_DATA_TYPE_UTILS_H #include #include #include #include #include "onnx/onnx_pb.h" namespace ONNX_NAMESPACE { // String pointer as unique TypeProto identifier. using DataType = const std::string*; namespace Utils { // Data type utility, which maintains a global type string to TypeProto map. // DataType (string pointer) is used as unique data type identifier for // efficiency. // // Grammar for data type string: // ::= | // tensor() | // seq() | // map(, ) // :: = float | int32 | string | bool | uint8 // | int8 | uint16 | int16 | int64 | float16 | double // // NOTE: ::= means the data is scalar (zero dimension). // // Example: float, tensor(float), etc. // class DataTypeUtils final { public: static DataType ToType(const std::string& type_str); static DataType ToType(const TypeProto& type_proto); static const TypeProto& ToTypeProto(const DataType& data_type); private: static void FromString(const std::string& type_str, TypeProto& type_proto); static void FromDataTypeString( const std::string& type_str, int32_t& tensor_data_type); static std::string ToString( const TypeProto& type_proto, const std::string& left = "", const std::string& right = ""); static std::string ToDataTypeString( int32_t tensor_data_type); static bool IsValidDataTypeString(const std::string& type_str); static std::unordered_map& GetTypeStrToProtoMap(); // Returns lock used for concurrent updates to TypeStrToProtoMap. static std::mutex& GetTypeStrLock(); }; } // namespace Utils } // namespace ONNX_NAMESPACE #endif // ! ONNX_DATA_TYPE_UTILS_H onnx-1.7.0/onnx/defs/generator/0000775000000000000000000000000013655345213015101 5ustar rootrootonnx-1.7.0/onnx/defs/generator/defs.cc0000664000000000000000000010430113655345213016330 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include "onnx/defs/function.h" #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { static const char* Constant_ver12_doc = R"DOC( This operator produces a constant tensor. Exactly one of the provided attributes, either value, sparse_value, or value_* must be specified. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Constant, 12, OpSchema() .SetDoc(Constant_ver12_doc) .Attr( "value", "The value for the elements of the output tensor.", AttributeProto::TENSOR, false) .Attr( "sparse_value", "The value for the elements of the output tensor in sparse format.", AttributeProto::SPARSE_TENSOR, false) .Attr( "value_int", "The value for the sole element for the scalar, int64, output tensor.", AttributeProto::INT, false) .Attr( "value_ints", "The values for the elements for the 1D, int64, output tensor.", AttributeProto::INTS, false) .Attr( "value_float", "The value for the sole element for the scalar, float32, output tensor.", AttributeProto::FLOAT, false) .Attr( "value_floats", "The values for the elements for the 1D, float32, output tensor.", AttributeProto::FLOATS, false) .Attr( "value_string", "The value for the sole element for the scalar, UTF-8 string, output tensor.", AttributeProto::STRING, false) .Attr( "value_strings", "The values for the elements for the 1D, UTF-8 string, output tensor.", AttributeProto::STRINGS, false) .Output( 0, "output", "Output tensor containing the same value of the provided tensor.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto* value = ctx.getAttribute("value"); auto* sparse_value = ctx.getAttribute("sparse_value"); auto* value_int = ctx.getAttribute("value_int"); auto* value_ints = ctx.getAttribute("value_ints"); auto* value_float = ctx.getAttribute("value_float"); auto* value_floats = ctx.getAttribute("value_floats"); auto* value_string = ctx.getAttribute("value_string"); auto* value_strings = ctx.getAttribute("value_strings"); std::vector non_null_attr = { (nullptr != value), (nullptr != sparse_value), (nullptr != value_int), (nullptr != value_ints), (nullptr != value_float), (nullptr != value_floats), (nullptr != value_string), (nullptr != value_strings) }; if (std::count(non_null_attr.begin(), non_null_attr.end(), true) != 1) fail_shape_inference( "One and only one of the attributes 'value', 'value_*' or 'sparse_value' must be specified for a Constant node."); if (nullptr != value) { // OpSchema::Verify check ensures that the attribute value has_t(): const TensorProto& tensor_proto = value->t(); updateOutputElemType(ctx, 0, tensor_proto.data_type()); updateOutputShape(ctx, 0, tensor_proto); return; } if (nullptr != value_int) { // OpSchema::Verify check ensures that the attribute value has_i(): if (!value_int->has_i()) fail_shape_inference("Attribute 'value_int' expect an integer.") updateOutputElemType(ctx, 0, TensorProto::INT64); updateOutputShape(ctx, 0, TensorShapeProto()); return; } if (nullptr != value_ints) { // OpSchema::Verify check ensures that the attribute value has ints. if (value_ints->ints_size() < 1) fail_shape_inference("Attribute 'value_ints' expect a list of integers.") updateOutputElemType(ctx, 0, TensorProto::INT64); appendDim(getOutputShape(ctx, 0), value_ints->ints_size()); return; } if (nullptr != value_float) { // OpSchema::Verify check ensures that the attribute value has_i(): if (!value_float->has_f()) fail_shape_inference("Attribute 'value_float' expect a float.") updateOutputElemType(ctx, 0, TensorProto::FLOAT); updateOutputShape(ctx, 0, TensorShapeProto()); return; } if (nullptr != value_floats) { // OpSchema::Verify check ensures that the attribute value has ints. if (value_floats->floats_size() < 1) fail_shape_inference("Attribute 'value_floats' expect a list of floats.") updateOutputElemType(ctx, 0, TensorProto::FLOAT); appendDim(getOutputShape(ctx, 0), value_floats->floats_size()); return; } if (nullptr != value_string) { // OpSchema::Verify check ensures that the attribute value has_i(): if (!value_string->has_s()) fail_shape_inference("Attribute 'value_string' expect a string.") updateOutputElemType(ctx, 0, TensorProto::STRING); updateOutputShape(ctx, 0, TensorShapeProto()); return; } if (nullptr != value_strings) { // OpSchema::Verify check ensures that the attribute value has ints. if (value_strings->strings_size() < 1) fail_shape_inference("Attribute 'value_strings' expect a list of strings.") updateOutputElemType(ctx, 0, TensorProto::STRING); appendDim(getOutputShape(ctx, 0), value_strings->strings_size()); return; } if (nullptr != sparse_value) { // OpSchema::Verify check ensures that the attribute value // has_sparse_tensor(): const SparseTensorProto& sparse = sparse_value->sparse_tensor(); // checker.cc::check_sparse_tensor checks that the sparse-value is // well-formed updateOutputElemType(ctx, 0, sparse.values().data_type()); auto* output_shape = getOutputShape(ctx, 0); for (int i = 0; i < sparse.dims_size(); ++i) appendDim(output_shape, sparse.dims(i)); return; } fail_shape_inference( "TypeAndShapeInferenceFunction implementation incomplete: " "this line should never be reached.") })); static const char* ConstantOfShape_ver9_doc = R"DOC( Generate a tensor with given value and shape. )DOC"; ONNX_OPERATOR_SET_SCHEMA( ConstantOfShape, 9, OpSchema() .SetDoc(ConstantOfShape_ver9_doc) .Attr( "value", "(Optional) The value of the output elements." "Should be a one-element tensor. If not specified, it defaults to a tensor of value 0 and datatype float32", AttributeProto::TENSOR, OPTIONAL_VALUE) .Input( 0, "input", "1D tensor. The shape of the expected output tensor. If empty tensor is given, the output would be a scalar." " All values must be >= 0.", "T1") .Output( 0, "output", "Output tensor of shape specified by 'input'." "If attribute 'value' is specified, the value and datatype of the output tensor is taken from 'value'." "If attribute 'value' is not specified, the value in the output defaults to 0, and the datatype " "defaults to float32.", "T2") .TypeConstraint("T1", {"tensor(int64)"}, "Constrain input types.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain output types to be numerics.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { if (ctx.getAttribute("value") != nullptr) { propagateElemTypeFromDtypeToOutput( ctx, ctx.getAttribute("value"), 0); } else { propagateElemTypeFromDtypeToOutput(ctx, TensorProto::FLOAT, 0); } // Shape inference based on input shape const TensorProto* targetShapeInitializer = ctx.getInputData(0); if (!targetShapeInitializer) { // This is the case when exact shape input is not available. // In this case, if the number of dimensions can be infered // from the input 'shape' tensor, then we add the same number // of dimensions (without any dim_value information) to the // output. if (ctx.getInputType(0)->tensor_type().has_shape()) { auto& input_shape = getInputShape(ctx, 0); auto input_shape_dim_size = input_shape.dim_size(); if (input_shape_dim_size > 1) { fail_shape_inference( "Shape input must be a one-dimensional tensor."); } if (input_shape.dim(0).has_dim_value()) { const auto& input_shape_dim_value = input_shape.dim(0).dim_value(); if (input_shape_dim_value > 0) { auto final_output_shape = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape(); for (int i = 0; i < input_shape_dim_value; ++i) { auto newdim = final_output_shape->add_dim(); (void)(newdim); // To eliminate "unused variable" compiler // warning. } } } } return; } // This is the second case when exact shape data is available. // In this case, we extract the shape values from input tensor // and create output tensor of that shape. // First, extract target shape value. std::vector targetShape; if (targetShapeInitializer->has_raw_data()) { const std::string& bytes = targetShapeInitializer->raw_data(); targetShape.insert( targetShape.end(), reinterpret_cast(bytes.c_str()), reinterpret_cast(bytes.c_str() + bytes.size())); } else { const auto& data = targetShapeInitializer->int64_data(); targetShape.insert(targetShape.end(), data.begin(), data.end()); } // Next, set output shape to the target shape. auto final_output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); for (const int64_t& targetShapeElem : targetShape) { if (targetShapeElem >= 0) { auto* new_dim = final_output_shape->add_dim(); new_dim->set_dim_value(targetShapeElem); } else { // Check if value is less than -1; fail if so fail_shape_inference("Invalid shape value: ", targetShapeElem); } } })); static const char* EyeLike_ver9_doc = R"DOC( Generate a 2D tensor (matrix) with ones on the diagonal and zeros everywhere else. Only 2D tensors are supported, i.e. input T1 must be of rank 2. The shape of the output tensor is the same as the input tensor. The data type can be specified by the 'dtype' argument. If 'dtype' is not specified, then the type of input tensor is used. By default, the main diagonal is populated with ones, but attribute 'k' can be used to populate upper or lower diagonals. The 'dtype' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message and be valid as an output type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( EyeLike, 9, OpSchema() .SetDoc(EyeLike_ver9_doc) .Attr( "k", "(Optional) Index of the diagonal to be populated with ones. Default is 0." " If T2 is the output, this op sets T2[i, i+k] = 1. k = 0 populates the main diagonal, " "k > 0 populates an upper diagonal, and k < 0 populates a lower diagonal.", AttributeProto::INT, static_cast(0)) .Attr( "dtype", "(Optional) The data type for the elements of the output tensor. If not specified," "the data type of the input tensor T1 is used. If input tensor T1 is also not" "specified, then type defaults to 'float'.", AttributeProto::INT, OPTIONAL_VALUE) .Input( 0, "input", "2D input tensor to copy shape, and optionally, type information from.", "T1") .Output( 0, "output", "Output tensor, same shape as input tensor T1.", "T2") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain input types. Strings and complex are not supported.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(bool)"}, "Constrain output types. Strings and complex are not supported.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { if (ctx.getAttribute("dtype") != nullptr) { propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0); } else { propagateElemTypeFromInputToOutput(ctx, 0, 0); } if (hasInputShape(ctx, 0)) { auto& input_shape = getInputShape(ctx, 0); if (input_shape.dim_size() != 2) { fail_shape_inference("Input tensor must be 2-dimensional"); } } propagateShapeFromInputToOutput(ctx, 0, 0); })); static const char* RandomUniform_ver1_doc = R"DOC( Generate a tensor with random values drawn from a uniform distribution. The shape of the tensor is specified by the `shape` argument and the range by `low` and `high`. The data type is specified by the 'dtype' argument. The 'dtype' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message. )DOC"; ONNX_OPERATOR_SET_SCHEMA( RandomUniform, 1, OpSchema() .SetDoc(RandomUniform_ver1_doc) .Attr( "low", "Lower boundary of the output values.", AttributeProto::FLOAT, 0.0f) .Attr( "high", "Upper boundary of the output values.", AttributeProto::FLOAT, 1.0f) .Attr( "seed", "(Optional) Seed to the random generator, if not specified we will auto generate one.", AttributeProto::FLOAT, OPTIONAL_VALUE) .Attr( "dtype", "The data type for the elements of the output tensor. If not specified, default is TensorProto::FLOAT.", AttributeProto::INT, static_cast(TensorProto::FLOAT)) .Attr("shape", "The shape of the output tensor.", AttributeProto::INTS) .Output( 0, "output", "Output tensor of random values drawn from uniform distribution", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0); propagateShapeFromAttributeToOutput(ctx, "shape", 0); })); static const char* RandomNormal_ver1_doc = R"DOC( Generate a tensor with random values drawn from a normal distribution. The shape of the tensor is specified by the `shape` argument and the parameter of the normal distribution specified by `mean` and `scale`. The data type is specified by the 'dtype' argument. The 'dtype' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message. )DOC"; ONNX_OPERATOR_SET_SCHEMA( RandomNormal, 1, OpSchema() .SetDoc(RandomNormal_ver1_doc) .Attr( "mean", "The mean of the normal distribution.", AttributeProto::FLOAT, 0.0f) .Attr( "scale", "The standard deviation of the normal distribution.", AttributeProto::FLOAT, 1.0f) .Attr( "seed", "(Optional) Seed to the random generator, if not specified we will auto generate one.", AttributeProto::FLOAT, OPTIONAL_VALUE) .Attr( "dtype", "The data type for the elements of the output tensor. Default is TensorProto::FLOAT.", AttributeProto::INT, static_cast(TensorProto::FLOAT)) .Attr("shape", "The shape of the output tensor.", AttributeProto::INTS) .Output( 0, "output", "Output tensor of random values drawn from normal distribution", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0); propagateShapeFromAttributeToOutput(ctx, "shape", 0); })); static const char* RandomUniformLike_ver1_doc = R"DOC( Generate a tensor with random values drawn from a uniform distribution. The shape of the output tensor is copied from the shape of the input tensor, and the parameters of the uniform distribution are specified by `low` and `high`. The data type is specified by the 'dtype' argument, or copied from the input tensor if not provided. The 'dtype' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message and be valid as an output type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( RandomUniformLike, 1, OpSchema() .SetDoc(RandomUniformLike_ver1_doc) .Attr( "low", "Lower boundary of the output values.", AttributeProto::FLOAT, 0.0f) .Attr( "high", "Upper boundary of the output values.", AttributeProto::FLOAT, 1.0f) .Attr( "seed", "(Optional) Seed to the random generator, if not specified we will auto generate one.", AttributeProto::FLOAT, OPTIONAL_VALUE) .Attr( "dtype", "(Optional) The data type for the elements of the output tensor, if not specified, we will use " "the data type of the input tensor.", AttributeProto::INT, OPTIONAL_VALUE) .Input( 0, "input", "Input tensor to copy shape and optionally type information from.", "T1") .Output( 0, "output", "Output tensor of random values drawn from uniform distribution", "T2") .TypeConstraint( "T1", OpSchema::all_tensor_types(), "Constrain to any tensor type. If the dtype attribute is not provided this must be a valid output type.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { if (ctx.getAttribute("dtype") != nullptr) propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0); else propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } propagateShapeFromInputToOutput(ctx, 0, 0); })); static const char* RandomNormalLike_ver1_doc = R"DOC( Generate a tensor with random values drawn from a normal distribution. The shape of the output tensor is copied from the shape of the input tensor, and the parameters of the normal distribution are specified by `mean` and `scale`. The data type is specified by the 'dtype' argument, or copied from the input tensor if not provided. The 'dtype' argument must be one of the data types specified in the 'DataType' enum field in the TensorProto message, and be valid as an output type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( RandomNormalLike, 1, OpSchema() .SetDoc(RandomNormalLike_ver1_doc) .Attr( "mean", "The mean of the normal distribution.", AttributeProto::FLOAT, 0.0f) .Attr( "scale", "The standard deviation of the normal distribution.", AttributeProto::FLOAT, 1.0f) .Attr( "seed", "(Optional) Seed to the random generator, if not specified we will auto generate one.", AttributeProto::FLOAT, OPTIONAL_VALUE) .Attr( "dtype", "(Optional) The data type for the elements of the output tensor, if not specified, we will use " "the data type of the input tensor.", AttributeProto::INT, OPTIONAL_VALUE) .Input( 0, "input", "Input tensor to copy shape and optionally type information from.", "T1") .Output( 0, "output", "Output tensor of random values drawn from normal distribution", "T2") .TypeConstraint( "T1", OpSchema::all_tensor_types(), "Constrain to any tensor type. If the dtype attribute is not provided this must be a valid output type.") .TypeConstraint( "T2", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { if (ctx.getAttribute("dtype") != nullptr) propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0); else propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasNInputShapes(ctx, 1)) { return; } propagateShapeFromInputToOutput(ctx, 0, 0); })); static const char* Multinomial_ver7_doc = R"DOC( Generate a tensor of samples from a multinomial distribution according to the probabilities of each of the possible outcomes. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Multinomial, 7, OpSchema() .SetDoc(Multinomial_ver7_doc) .Attr( "sample_size", "Number of times to sample.", AttributeProto::INT, static_cast(1)) .Attr( "seed", "(Optional) Seed to the random generator, if not specified we will auto generate one.", AttributeProto::FLOAT, OPTIONAL_VALUE) .Attr( "dtype", "(Optional) The data type for the elements of the output tensor, if not specified, we will use int32.", AttributeProto::INT, static_cast(TensorProto::INT32)) .Input( 0, "input", "Input tensor with shape [batch_size, class_size], where class_size is the number of all possible outcomes. Each value along the axis zero represents the unnormalized log-probability of each corresponding outcome in a batch.", "T1") .Output( 0, "output", "Output tensor with shape [batch_size, sample_size], where sample_size is the number of times to sample. Each value along the axis zero represents the outcome of the corresponding sample in a batch.", "T2") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input types to float tensors.") .TypeConstraint( "T2", {"tensor(int32)", "tensor(int64)"}, "Constrain output types to integral tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto dtype = ctx.getAttribute("dtype"); auto dataType = TensorProto_DataType::TensorProto_DataType_INT32; if (dtype != nullptr) { dataType = static_cast(dtype->i()); if (dataType != TensorProto_DataType::TensorProto_DataType_INT32 && dataType != TensorProto_DataType::TensorProto_DataType_INT64) fail_type_inference("Output type must be int32 or int64"); } updateOutputElemType(ctx, 0, dataType); TensorShapeProto::Dimension batch_size, sample_size; if (hasInputShape(ctx, 0)) { auto& input_shape = getInputShape(ctx, 0); if (input_shape.dim_size() != 2) fail_shape_inference("Input tensor must have rank 2"); batch_size = input_shape.dim(0); } // else statically-unknown batch-size sample_size.set_dim_value(getAttribute(ctx, "sample_size", 1)); updateOutputShape(ctx, 0, {batch_size, sample_size}); })); static const char* Range_ver11_doc = R"DOC( Generate a tensor containing a sequence of numbers that begin at `start` and extends by increments of `delta` up to `limit` (exclusive). The number of elements in the output of range is computed as below- `number_of_elements = max( ceil( (limit - start) / delta ) , 0 )` The pseudocode determining the contents of the output is shown below- `for(int i=0; i inline int64_t compute_output_dim_for_range( const TensorProto* start, const TensorProto* limit, const TensorProto* delta) { if (start->dims().size() != 0 || limit->dims().size() != 0 || delta->dims().size() != 0) { fail_shape_inference( "Input to 'Range' op should be scalars (Tensor with only one element and shape empty)"); } const auto& start_data = ParseData(start); const auto& limit_data = ParseData(limit); const auto& delta_data = ParseData(delta); int64_t n = static_cast( ceil((1.0 * (limit_data[0] - start_data[0])) / delta_data[0])); if (n < 0) n = 0; return n; } const std::vector build_nodes_range_op() { // body for 'Loop node' GraphProto loop_sub_graph; loop_sub_graph.set_name("loop_body_attribute"); // 'Loop' node 'body' attribute's graph inputs // input 0 - number of iteration auto* input_value_info_proto_0 = loop_sub_graph.add_input(); input_value_info_proto_0->set_name("i"); // add an empty shape auto* input_0_type_proto_tensor = input_value_info_proto_0->mutable_type()->mutable_tensor_type(); input_0_type_proto_tensor->mutable_shape()->Clear(); // always INT64 type input_0_type_proto_tensor->set_elem_type(TensorProto_DataType_INT64); // input 1 - condition auto* input_value_info_proto_1 = loop_sub_graph.add_input(); input_value_info_proto_1->set_name("cond"); // add an empty shape auto* input_1_type_proto_tensor = input_value_info_proto_1->mutable_type()->mutable_tensor_type(); input_1_type_proto_tensor->mutable_shape()->Clear(); // always BOOL type input_1_type_proto_tensor->set_elem_type(TensorProto_DataType_BOOL); // input 2 - loop carried dependency auto* input_value_info_proto_2 = loop_sub_graph.add_input(); input_value_info_proto_2->set_name("prev"); // 'Loop' node 'body' attribute's graph nodes auto* node_proto_0 = loop_sub_graph.add_node(); node_proto_0->set_op_type("Identity"); node_proto_0->add_input(); node_proto_0->set_input(0, "cond"); node_proto_0->add_output(); node_proto_0->set_output(0, "cond_out"); auto* node_proto_1 = loop_sub_graph.add_node(); node_proto_1->set_op_type("Add"); node_proto_1->add_input(); node_proto_1->set_input(0, "prev"); node_proto_1->add_input(); node_proto_1->set_input(1, "delta"); node_proto_1->add_output(); node_proto_1->set_output(0, "current"); auto* node_proto_2 = loop_sub_graph.add_node(); node_proto_2->set_op_type("Identity"); node_proto_2->add_input(); node_proto_2->set_input(0, "prev"); node_proto_2->add_output(); node_proto_2->set_output(0, "range"); // 'Loop' node 'body' attribute's graph inputs auto* output_value_info_proto_0 = loop_sub_graph.add_output(); output_value_info_proto_0->set_name("cond_out"); auto* output_value_info_proto_1 = loop_sub_graph.add_output(); output_value_info_proto_1->set_name("current"); auto* output_value_info_proto_2 = loop_sub_graph.add_output(); output_value_info_proto_2->set_name("range"); return FunctionBodyHelper::BuildNodes( {// nodes: {outputs, op, inputs, attributes} {{"sub_result"}, "Sub", {"limit", "start"}}, {{"sub_result_casted"}, "Cast", {"sub_result"}, {{"to", static_cast(1)}}}, {{"delta_casted"}, "Cast", {"delta"}, {{"to", static_cast(1)}}}, {{"div_result"}, "Div", {"sub_result_casted", "delta_casted"}}, {{"ceil_result"}, "Ceil", {"div_result"}}, // we want max(0, ceil_cast_int) as negative values would evaluate to // bool true in next step {{"ceil_result_relu"}, "Relu", {"ceil_result"}}, {{"ceil_result_relu_int"}, "Cast", {"ceil_result_relu"}, {{"to", static_cast(7)}}}, {{"ceil_result_relu_bool"}, "Cast", {"ceil_result_relu"}, {{"to", static_cast(9)}}}, {{"variadic_output", "output"}, "Loop", {"ceil_result_relu_int", "ceil_result_relu_bool", "start"}, {MakeAttribute("body", loop_sub_graph)}}}); } ONNX_OPERATOR_SET_SCHEMA( Range, 11, OpSchema() .SetDoc(Range_ver11_doc) .Input( 0, "start", "Scalar. First entry for the range of output values.", "T") .Input( 1, "limit", "Scalar. Exclusive upper limit for the range of output values.", "T") .Input(2, "delta", "Scalar. Value to step by.", "T") .Output( 0, "output", "A 1-D tensor with same type as the inputs containing generated range of values.", "T") .TypeConstraint( "T", {"tensor(float)", "tensor(double)", "tensor(int16)", "tensor(int32)", "tensor(int64)"}, "Constrain input types to common numeric type tensors.") .FunctionBody(build_nodes_range_op()) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference const auto* start_initializer = ctx.getInputData(0); const auto* limit_initializer = ctx.getInputData(1); const auto* delta_initializer = ctx.getInputData(2); // Output is always 1-D auto* output_dim = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape() ->add_dim(); // If any of Range's inputs are not initializers, the output dimension // value would remain unknown. if (start_initializer != nullptr && limit_initializer != nullptr && delta_initializer != nullptr) { // Make sure the input types are homogeneous if ((start_initializer->data_type() != limit_initializer->data_type()) || (start_initializer->data_type() != delta_initializer->data_type())) { fail_shape_inference( "All inputs to 'Range' op must be of the same type"); } // Explicitly compute the output dimension if Range's inputs are // stored in initializer list. if (start_initializer->data_type() == TensorProto::FLOAT) { output_dim->set_dim_value(compute_output_dim_for_range( start_initializer, limit_initializer, delta_initializer)); } else if (start_initializer->data_type() == TensorProto::INT32) { output_dim->set_dim_value(compute_output_dim_for_range( start_initializer, limit_initializer, delta_initializer)); } else if (start_initializer->data_type() == TensorProto::INT64) { output_dim->set_dim_value(compute_output_dim_for_range( start_initializer, limit_initializer, delta_initializer)); } else if (start_initializer->data_type() == TensorProto::DOUBLE) { output_dim->set_dim_value(compute_output_dim_for_range( start_initializer, limit_initializer, delta_initializer)); } else { // 'float16' has no native CPU type - // stop with rank inference, no action here } return; } })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/generator/old.cc0000664000000000000000000001125513655345213016172 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { static const char* Constant_ver1_doc = R"DOC(A constant tensor.)DOC"; ONNX_OPERATOR_SET_SCHEMA( Constant, 1, OpSchema() .SetDoc(Constant_ver1_doc) .Attr( "value", "The value for the elements of the output tensor.", AttributeProto::TENSOR) .Output( 0, "output", "Output tensor containing the same value of the provided tensor.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto attr_proto = ctx.getAttribute("value"); if (nullptr == attr_proto) return; // attribute not present if (!attr_proto->has_t()) return; // attribute has no tensor value const TensorProto& tensor_proto = attr_proto->t(); updateOutputElemType(ctx, 0, tensor_proto.data_type()); updateOutputShape(ctx, 0, tensor_proto); })); static const char* Constant_ver9_doc = R"DOC(A constant tensor.)DOC"; ONNX_OPERATOR_SET_SCHEMA( Constant, 9, OpSchema() .SetDoc(Constant_ver9_doc) .Attr( "value", "The value for the elements of the output tensor.", AttributeProto::TENSOR) .Output( 0, "output", "Output tensor containing the same value of the provided tensor.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto attr_proto = ctx.getAttribute("value"); if (nullptr == attr_proto || !attr_proto->has_t()) fail_shape_inference( "Attribute 'value' of Constant node must exist with 'Tensor' data."); const TensorProto& tensor_proto = attr_proto->t(); updateOutputElemType(ctx, 0, tensor_proto.data_type()); updateOutputShape(ctx, 0, tensor_proto); })); static const char* Constant_ver11_doc = R"DOC( A constant tensor. Exactly one of the two attributes, either value or sparse_value, must be specified. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Constant, 11, OpSchema() .SetDoc(Constant_ver11_doc) .Attr( "value", "The value for the elements of the output tensor.", AttributeProto::TENSOR, false) .Attr( "sparse_value", "The value for the elements of the output tensor in sparse format.", AttributeProto::SPARSE_TENSOR, false) .Output( 0, "output", "Output tensor containing the same value of the provided tensor.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output types to all tensor types.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto* value = ctx.getAttribute("value"); auto* sparse_value = ctx.getAttribute("sparse_value"); if ((nullptr != value) && (nullptr != sparse_value)) fail_shape_inference( "Only one of the attributes 'value' or 'sparse_value' must be specified for a Constant node."); if (nullptr != value) { // OpSchema::Verify check ensures that the attribute value has_t(): const TensorProto& tensor_proto = value->t(); updateOutputElemType(ctx, 0, tensor_proto.data_type()); updateOutputShape(ctx, 0, tensor_proto); return; } if (nullptr != sparse_value) { // OpSchema::Verify check ensures that the attribute value // has_sparse_tensor(): const SparseTensorProto& sparse = sparse_value->sparse_tensor(); // checker.cc::check_sparse_tensor checks that the sparse-value is // well-formed updateOutputElemType(ctx, 0, sparse.values().data_type()); auto* output_shape = getOutputShape(ctx, 0); for (int i = 0; i < sparse.dims_size(); ++i) appendDim(output_shape, sparse.dims(i)); return; } fail_shape_inference( "One of the attributes 'value' or 'sparse_value' must be specified for a Constant node.") })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/nn/0000775000000000000000000000000013655345213013526 5ustar rootrootonnx-1.7.0/onnx/defs/nn/old.cc0000664000000000000000000021431313655345213014617 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { const char* pads_doc2 = "Padding for the beginning and ending along each spatial axis, it can take any value greater " "than or equal to 0. The value represent the number of pixels added to the beginning " "and end part of the corresponding axis. `pads` format should be as follow " "[x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of pixels " "added at the beginning of axis `i` and xi_end, the number of pixels added at " "the end of axis `i`. This attribute cannot be used simultaneously with " "auto_pad attribute. If not present, the padding defaults to 0 along start and end of each spatial axis."; const char* auto_pad_doc2 = "auto_pad must be either NOTSET, SAME_UPPER, SAME_LOWER or VALID. Where " "default value is NOTSET, which means explicit padding is used. " "SAME_UPPER or SAME_LOWER mean pad the input so that the output spatial size match the input." "In case of odd number add the extra padding at the end for SAME_UPPER and at the " "beginning for SAME_LOWER. VALID mean no padding."; void convPoolShapeInference1( InferenceContext& ctx, bool use_dilation, bool require_kernel_shape, int input1Idx, int input2Idx) { // we need the first input shape for this inference. if (!hasInputShape(ctx, input1Idx)) { return; } // if kernel shape is an input (and not attribute) // we need the shape of the second input. if (!require_kernel_shape && !hasInputShape(ctx, input2Idx)) { return; } auto input_shape = ctx.getInputType(input1Idx)->tensor_type().shape(); if (input_shape.dim_size() < 2) { fail_shape_inference("Input tensor must have atleast 2 dimensions"); } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); // Only MaxPool and Conv support dilation. For // simplicity of the code, we just treat the rest of them as having all-1s // dilation. std::vector dilations; if (use_dilation && getRepeatedAttribute(ctx, "dilations", dilations)) { if (dilations.size() != n_input_dims) { fail_shape_inference("Attribute dilations has incorrect size"); } } else { dilations.assign(n_input_dims, 1); } std::vector strides; if (getRepeatedAttribute(ctx, "strides", strides)) { if (strides.size() != n_input_dims) { fail_shape_inference("Attribute strides has incorrect size"); } } else { strides.assign(n_input_dims, 1); } std::vector kernel_shape; if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) { if (kernel_shape.size() != n_input_dims) { fail_shape_inference("Attribute kernel_shape has incorrect size"); } } else if (require_kernel_shape) { fail_shape_inference("Attribute kernel_shape must be specified"); } else { auto second_input_shape = ctx.getInputType(input2Idx)->tensor_type().shape(); for (int i = 2; i < second_input_shape.dim_size(); ++i) { if (!second_input_shape.dim(i).has_dim_value()) { return; } kernel_shape.push_back(second_input_shape.dim(i).dim_value()); } } std::vector effective_kernel_shape = kernel_shape; for (int i = 0; i < static_cast(kernel_shape.size()); i++) { // accounting for dilation, how big is the kernel in this dimension effective_kernel_shape[i] = (effective_kernel_shape[i] - 1) * dilations[i] + 1; } std::vector pads; if (getRepeatedAttribute(ctx, "pads", pads)) { if (pads.size() != n_input_dims * 2) { fail_shape_inference("Attribute pads has incorrect size"); } } else { pads.assign(n_input_dims * 2, 0); const auto* auto_pad_attr = ctx.getAttribute("auto_pad"); if ((nullptr != auto_pad_attr) && (auto_pad_attr->s() != "VALID")) { int input_dims_size = static_cast(n_input_dims); for (int i = 0; i < input_dims_size; ++i) { int64_t residual = 0; int64_t stride = strides[i]; if (stride > 1) { if (!input_shape.dim(2 + i).has_dim_value()) { continue; } residual = input_shape.dim(2 + i).dim_value(); while (residual >= stride) { residual -= stride; } } int64_t total_pad = residual == 0 ? effective_kernel_shape[i] - stride : effective_kernel_shape[i] - residual; if (total_pad < 0) total_pad = 0; int64_t half_pad_small = total_pad >> 1; int64_t half_pad_big = total_pad - half_pad_small; if (auto_pad_attr->s() == "SAME_UPPER") { pads[i] = half_pad_small; pads[i + input_dims_size] = half_pad_big; } else if (auto_pad_attr->s() == "SAME_LOWER") { pads[i] = half_pad_big; pads[i + input_dims_size] = half_pad_small; } } } } auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); if (require_kernel_shape) { // add the first two dimensions from the input. *output_shape->add_dim() = input_shape.dim(0); *output_shape->add_dim() = input_shape.dim(1); } else { *output_shape->add_dim() = input_shape.dim(0); auto& second_input_shape = getInputShape(ctx, input2Idx); if (second_input_shape.dim_size() < 1) { fail_shape_inference("Second input tensor has wrong dimension"); } *output_shape->add_dim() = second_input_shape.dim(0); } int kernel_shape_size = static_cast(kernel_shape.size()); for (int i = 0; i < kernel_shape_size; ++i) { auto newdim = output_shape->add_dim(); if (!input_shape.dim(2 + i).has_dim_value()) { continue; } // how big is the input, including padding int64_t effective_input_size = input_shape.dim(2 + i).dim_value(); effective_input_size += pads[i]; effective_input_size += pads[i + kernel_shape_size]; // default is floor mode .i.e. ceil_mode is set to 0 auto ceil_mode = getAttribute(ctx, "ceil_mode", 0); // how many times we can move the kernel from it's initial position, based // on the stride int64_t strided_kernel_positions; if (ceil_mode == 1) strided_kernel_positions = (int64_t)(std::ceil( (effective_input_size - effective_kernel_shape[i]) / float(strides[i]))); else strided_kernel_positions = (effective_input_size - effective_kernel_shape[i]) / strides[i]; // add in the initial position newdim->set_dim_value(1 + strided_kernel_positions); } if (ctx.getNumOutputs() > 1) { // MaxPool with two outputs case. auto second_output_shape = ctx.getOutputType(1)->mutable_tensor_type()->mutable_shape(); second_output_shape->CopyFrom(*output_shape); } } std::function PoolOpSchemaGenerator_9( const char* name, const char* opName, const char* additionalDescription) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( {name} consumes an input tensor X and applies {opName} pooling across the tensor according to kernel sizes, stride sizes, and pad lengths. {opName} pooling consisting of computing the {opName} on all values of a subset of the input tensor according to the kernel size and downsampling the data into the output tensor Y for further processing. The output spatial shape will be following: ``` output_spatial_shape[i] = floor((input_spatial_shape[i] + pad_shape[i] - kernel_spatial_shape[i]) / strides_spatial_shape[i] + 1) * pad_shape[i] is sum of pads along axis i ``` `auto_pad` is a DEPRECATED attribute. If you are using them currently, the output spatial shape will be following: ``` VALID: output_spatial_shape[i] = ceil((input_spatial_shape[i] - kernel_spatial_shape[i] + 1) / strides_spatial_shape[i]) SAME_UPPER or SAME_LOWER: output_spatial_shape[i] = ceil(input_spatial_shape[i] / strides_spatial_shape[i]) ``` And pad shape will be following if `SAME_UPPER` or `SAME_LOWER`: ``` pad_shape[i] = (output_spatial_shape[i] - 1) * strides_spatial_shape[i] + kernel_spatial_shape[i] - input_spatial_shape[i] ``` {additionalDescription} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{opName}", opName); ReplaceAll(doc, "{additionalDescription}", additionalDescription);); schema.SetDoc(doc); schema.Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS); schema.Attr( "strides", "Stride along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc2, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc2, AttributeProto::INTS, OPTIONAL_VALUE); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size. Optionally, if dimension denotation is " "in effect, the operation expects the input " "data tensor to arrive with the dimension denotation " "of [DATA_BATCH, DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T"); schema.Output( 0, "Y", "Output data tensor from average or max pooling across " "the input tensor. Dimensions will vary based " "on various kernel, stride, and pad sizes. Floor value of " "the dimension is used", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (ctx.getNumOutputs() > 1) { // MaxPool with two outputs case. auto output_type = ctx.getOutputType(1); if (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET) { output_type->mutable_tensor_type()->set_elem_type(TensorProto::INT64); } } convPoolShapeInference1(ctx, false, true, 0, 1); }); }; } std::function PoolOpSchemaGenerator_10( const char* name, const char* opName, const char* additionalDescription, bool use_dilation, int opsetNum) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( {name} consumes an input tensor X and applies {opName} pooling across the tensor according to kernel sizes, stride sizes, and pad lengths. {opName} pooling consisting of computing the {opName} on all values of a subset of the input tensor according to the kernel size and downsampling the data into the output tensor Y for further processing. The output spatial shape will be following: ``` output_spatial_shape[i] = floor((input_spatial_shape[i] + pad_shape[i] - {kernelSpatialShape}) / strides_spatial_shape[i] + 1) ``` or ``` output_spatial_shape[i] = ceil((input_spatial_shape[i] + pad_shape[i] - {kernelSpatialShape}) / strides_spatial_shape[i] + 1) ``` if ceil_mode is enabled ``` * pad_shape[i] is sum of pads along axis i ``` `auto_pad` is a DEPRECATED attribute. If you are using them currently, the output spatial shape will be following: ``` VALID: output_spatial_shape[i] = ceil((input_spatial_shape[i] - {kernelSpatialShape} + 1) / strides_spatial_shape[i]) SAME_UPPER or SAME_LOWER: output_spatial_shape[i] = ceil(input_spatial_shape[i] / strides_spatial_shape[i]) ``` And pad shape will be following if `SAME_UPPER` or `SAME_LOWER`: ``` pad_shape[i] = (output_spatial_shape[i] - 1) * strides_spatial_shape[i] + {kernelSpatialShape} - input_spatial_shape[i] ``` {additionalDescription} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{opName}", opName); ReplaceAll(doc, "{additionalDescription}", additionalDescription); ReplaceAll( doc, "{kernelSpatialShape}", use_dilation ? "((kernel_spatial_shape[i] - 1) * dilations[i] + 1)" : "kernel_spatial_shape[i]");); schema.SetDoc(doc); schema.Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS); schema.Attr( "strides", opsetNum == 11 ? "Stride along each spatial axis. If not present, the stride defaults to 1 along each spatial axis." : "Stride along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc2, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc2, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "ceil_mode", "Whether to use ceil or floor (default) to compute the output shape.", AttributeProto::INT, static_cast(0)); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size. Optionally, if dimension denotation is " "in effect, the operation expects the input " "data tensor to arrive with the dimension denotation " "of [DATA_BATCH, DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T"); schema.Output( 0, "Y", "Output data tensor from average or max pooling across " "the input tensor. Dimensions will vary based " "on various kernel, stride, and pad sizes. Floor value of " "the dimension is used", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([use_dilation](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (ctx.getNumOutputs() > 1) { // MaxPool with two outputs case. auto output_type = ctx.getOutputType(1); if (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET) { output_type->mutable_tensor_type()->set_elem_type(TensorProto::INT64); } } convPoolShapeInference1(ctx, use_dilation, true, 0, 1); }); }; } ONNX_OPERATOR_SET_SCHEMA( AveragePool, 1, OpSchema().FillUsing(PoolOpSchemaGenerator_9( "AveragePool", "average", "The output of each pooling window is divided by the number of elements exclude pad."))); ONNX_OPERATOR_SET_SCHEMA( AveragePool, 7, OpSchema() .FillUsing(PoolOpSchemaGenerator_9( "AveragePool", "average", "The output of each pooling window is divided by the number of elements (exclude pad when attribute count_include_pad is zero).")) .Attr( "count_include_pad", "Whether include pad pixels when calculating values for the edges. Default is 0, doesn't count include pad.", AttributeProto::INT, static_cast(0))); ONNX_OPERATOR_SET_SCHEMA( AveragePool, 10, OpSchema() .FillUsing(PoolOpSchemaGenerator_10( "AveragePool", "average", "The output of each pooling window is divided by the number of elements (exclude pad when attribute count_include_pad is zero).", false, 10)) .Attr( "count_include_pad", "Whether include pad pixels when calculating values for the edges. Default is 0, doesn't count include pad.", AttributeProto::INT, static_cast(0))); ONNX_OPERATOR_SET_SCHEMA( MaxPool, 1, OpSchema().FillUsing(PoolOpSchemaGenerator_9( "MaxPool", "max", "The output of each pooling window is maximum number of elements exclude pad."))); ONNX_OPERATOR_SET_SCHEMA( MaxPool, 8, OpSchema() .FillUsing(PoolOpSchemaGenerator_9( "MaxPool", "max", "The output of each pooling window is maximum number of elements exclude pad.")) .Attr( "storage_order", "The storage order of the tensor. 0 is row major, and 1 is column major.", AttributeProto::INT, static_cast(0)) .Output( 1, "Indices", "Indices tensor from max pooling across the input tensor. " "The dimensions of indices are the same as output tensor. " "The values in indices of are the indices of the selected values during pooling. " "The indices are computed as flatten 1-D tensor, " "and the indices do not consider padding. " "So the values in indices are in [0, N x C x D1 x ... x Dn).", "I", OpSchema::Optional) .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64")); ONNX_OPERATOR_SET_SCHEMA( MaxPool, 10, OpSchema() .FillUsing(PoolOpSchemaGenerator_10( "MaxPool", "max", "The output of each pooling window is maximum number of elements exclude pad.", true, 10)) .Attr( "storage_order", "The storage order of the tensor. 0 is row major, and 1 is column major.", AttributeProto::INT, static_cast(0)) .Attr( "dilations", "Dilation value along each spatial axis of filter.", AttributeProto::INTS, OPTIONAL_VALUE) .Output( 1, "Indices", "Indices tensor from max pooling across the input tensor. " "The dimensions of indices are the same as output tensor. " "The values in indices of are the indices of the selected values during pooling. " "The indices are computed as flatten 1-D tensor, " "and the indices do not consider padding. " "So the values in indices are in [0, N x C x D1 x ... x Dn).", "I", OpSchema::Optional) .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64")); ONNX_OPERATOR_SET_SCHEMA( MaxPool, 11, OpSchema() .FillUsing(PoolOpSchemaGenerator_10( "MaxPool", "max", "The output of each pooling window is maximum number of elements exclude pad.", true, 11)) .Attr( "storage_order", "The storage order of the tensor. 0 is row major, and 1 is column major.", AttributeProto::INT, static_cast(0)) .Attr( "dilations", "Dilation value along each spatial axis of filter. If not present, the dilation defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Output( 1, "Indices", "Indices tensor from max pooling across the input tensor. " "The dimensions of indices are the same as output tensor. " "The values in indices of are the indices of the selected values during pooling. " "The indices are computed as flatten 1-D tensor, " "and the indices do not consider padding. " "So the values in indices are in [0, N x C x D1 x ... x Dn).", "I", OpSchema::Optional) .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64")); void maxUnpoolShapeInference1(InferenceContext& ctx) { // we need at least two inputs to have a shape for this inference. if (ctx.getNumInputs() != 2 && ctx.getNumInputs() != 3) { fail_type_inference("MaxUnpool op must have either two or three inputs."); } propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasInputShape(ctx, 0)) { return; // If first input does not have shape, we cannot infer much. } auto input_shape = ctx.getInputType(0)->tensor_type().shape(); if (input_shape.dim_size() < 2) { fail_shape_inference("Input tensor X must have atleast 2 dimensions."); } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); std::vector pads; if (getRepeatedAttribute(ctx, "pads", pads)) { if (pads.size() != n_input_dims * 2) { fail_shape_inference("Attribute pads has incorrect size."); } } else { pads.assign(n_input_dims * 2, 0); } std::vector strides; if (getRepeatedAttribute(ctx, "strides", strides)) { if (strides.size() != n_input_dims) { fail_shape_inference("Attribute strides has incorrect size."); } } else { strides.assign(n_input_dims, 1); } std::vector kernel_shape; if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) { if (kernel_shape.size() != n_input_dims) { fail_shape_inference("Attribute kernel_shape has incorrect size."); } } else { fail_shape_inference("Attribute kernel_shape must be specified."); } if (ctx.getNumInputs() == 3) { // If the third input, output_size, is specified, then use that instead // of inferring shape from inputs. if (hasInputShape(ctx, 2)) { auto& output_shape = getInputShape(ctx, 2); if (output_shape.dim_size() != 1) { fail_type_inference("'output_shape' must be rank 1 tensor."); } if (output_shape.dim((int)0).has_dim_value() && static_cast(output_shape.dim((int)0).dim_value()) != input_shape.dim_size()) { fail_shape_inference( "'output_shape' must have same number of elements as the shape of input tensor X."); } } return; // 'output_shape' is specified as input. Actual shape will be // determined at runtime. } auto final_output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); *final_output_shape->add_dim() = input_shape.dim(0); *final_output_shape->add_dim() = ctx.getInputType(1)->tensor_type().shape().dim( 1); // channels should be the second dim of second input. int kernel_shape_size = static_cast(kernel_shape.size()); for (int i = 0; i < kernel_shape_size; ++i) { auto newdim = final_output_shape->add_dim(); if (!input_shape.dim(2 + i).has_dim_value()) { continue; } int64_t newdim_value = strides[i] * (input_shape.dim(2 + i).dim_value() - 1); newdim_value += kernel_shape[i]; newdim_value -= pads[i]; newdim_value -= pads[i + kernel_shape_size]; // add in the initial position newdim->set_dim_value(newdim_value); } } static const char* MaxUnpool_ver9_doc = R"DOC( MaxUnpool essentially computes the partial inverse of the MaxPool op. The input information to this op is typically the the output information from a MaxPool op. The first input tensor X is the tensor that needs to be unpooled, which is typically the pooled tensor (first output) from MaxPool. The second input tensor, I, contains the indices to the (locally maximal) elements corrsponding to the elements in the first input tensor X. Input tensor I is typically the second output of the MaxPool op. The third (optional) input is a tensor that specifies the output size of the unpooling operation. MaxUnpool is intended to do 'partial' inverse of the MaxPool op. 'Partial' because all the non-maximal values from the original input to MaxPool are set to zero in the output of the MaxUnpool op. Pooling the result of an unpooling operation should give back the original input to the unpooling op. MaxUnpool can produce the same output size for several input sizes, which makes unpooling op ambiguous. The third input argument, output_size, is meant to disambiguate the op and produce output tensor of known/predictable size. In addition to the inputs, MaxUnpool takes three attributes, namely kernel_shape, strides, and pads, which define the exact unpooling op. The attributes typically have the same values as the corrsponding pooling op that the unpooling op is trying to invert. )DOC"; ONNX_OPERATOR_SET_SCHEMA( MaxUnpool, 9, OpSchema() .SetDoc(MaxUnpool_ver9_doc) .Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS) .Attr( "strides", "Stride along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr("pads", pads_doc2, AttributeProto::INTS, OPTIONAL_VALUE) .Input( 0, "X", "Input data tensor that has to be unpooled. " "This tensor is typically the first output of the MaxPool op." "Dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non-image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size. Optionally, if dimension denotation is " "in effect, the operation expects the input " "data tensor to arrive with the dimension denotation " "of [DATA_BATCH, DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T1") .Input( 1, "I", "Input data tensor containing the indices corresponding to " "elements in the first input tensor X." "This tensor is typically the second output of the MaxPool op." "Dimensions must be the same as input tensor X. " "The indices are linear, i.e. computed considering the tensor as flattened 1-D tensor, " "assuming row-major storage. Also, the linear indices should not consider padding. " "So the values in indices are in the range [0, N x C x D1 x ... x Dn).", "T2") .Input( 2, "output_shape", "The shape of the output can be explicitly set which will cause pads values to be auto generated. If 'output_shape' is specified, " "'pads' values are ignored.", "T2", OpSchema::Optional) .Output( 0, "output", "Output data tensor that contains the result of the unpooling.", "T1") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "T2", {"tensor(int64)"}, "Constrain index tensor to int64") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { maxUnpoolShapeInference1(ctx); })); const char* pads_doc1 = "Padding for the beginning and ending along each axis, it can take any value greater " "than or equal to 0. The value represent the number of pixels added to the beginning " "and end part of the corresponding axis. `pads` format should be as follow " "[x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of pixels " "added at the beginning of axis `i` and xi_end, the number of pixels added at " "the end of axis `i`. This attribute cannot be used simultaneously with " "auto_pad attribute."; const char* auto_pad_doc1 = "auto_pad must be either NOTSET, SAME_UPPER, SAME_LOWER or VALID. Where " "default value is NOTSET, which means explicit padding is used. " "SAME_UPPER or SAME_LOWER mean pad the input so that the output size match the input." "In case of odd number add the extra padding at the end for SAME_UPPER and at the " "beginning for SAME_LOWER. VALID mean no padding. DEPRECATION NOTE: auto_pad is " "only intended to support legacy uses, and for framework authors, one is explicitly " "encouraged to use explicit padding specified in the pads attribute."; static const char* LpPool_ver1_doc = R"DOC( LpPool consumes an input tensor X and applies Lp pooling across the the tensor according to kernel sizes, stride sizes, and pad lengths. Lp pooling consisting of computing the Lp norm on all values of a subset of the input tensor according to the kernel size and downsampling the data into the output tensor Y for further processing.)DOC"; ONNX_OPERATOR_SET_SCHEMA( LpPool, 1, OpSchema() .SetDoc(LpPool_ver1_doc) .Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "strides", "Stride along each axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "auto_pad", auto_pad_doc1, AttributeProto::STRING, std::string("NOTSET")) .Attr("pads", pads_doc1, AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "p", "p value of the Lp norm used to pool over the input data, default is 2.0.", AttributeProto::FLOAT, 2.0f) .Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimension are in the form of " "(N x C x D1 x D2 ... Dn), where N is the " "batch size.", "T") .Output( 0, "Y", "Output data tensor from Lp pooling across the input " "tensor. Dimensions will vary based on various kernel, stride, and pad " "sizes.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); std::function LpPoolOpSchemaGenerator_10(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( {name} consumes an input tensor X and applies Lp pooling across the tensor according to kernel sizes, stride sizes, and pad lengths. Lp pooling consisting of computing the Lp norm on all values of a subset of the input tensor according to the kernel size and downsampling the data into the output tensor Y for further processing.)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc); schema.Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS); schema.Attr( "strides", "Stride along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc2, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc2, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "p", "p value of the Lp norm used to pool over the input data.", AttributeProto::INT, static_cast(2)); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the " "batch size.", "T"); schema.Output( 0, "Y", "Output data tensor from Lp pooling across the input " "tensor. Dimensions will vary based on various kernel, stride, and pad " "sizes.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); convPoolShapeInference1(ctx, false, true, 0, 1); }); }; } ONNX_OPERATOR_SET_SCHEMA( LpPool, 2, OpSchema().FillUsing(LpPoolOpSchemaGenerator_10("LpPool"))); static const char* GlobalLpPool_ver1_doc = R"DOC( GlobalLpPool consumes an input tensor X and applies lp pool pooling across the the values in the same channel. This is equivalent to LpPool with kernel size equal to the spatial dimension of input tensor.)DOC"; std::function ConvOpSchemaGenerator_10( const char* filter_desc) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( The convolution operator consumes an input tensor and {filter_desc}, and computes the output.)DOC"; ReplaceAll(doc, "{filter_desc}", filter_desc);); schema.SetDoc(doc); schema.Input( 0, "X", "Input data tensor from previous layer; " "has size (N x C x H x W), where N is the batch size, " "C is the number of channels, and H and W are the " "height and width. Note that this is for the 2D image. " "Otherwise the size is (N x C x D1 x D2 ... x Dn). " "Optionally, if dimension denotation is " "in effect, the operation expects input data tensor " "to arrive with the dimension denotation of [DATA_BATCH, " "DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T"); schema.Input( 1, "W", "The weight tensor that will be used in the " "convolutions; has size (M x C/group x kH x kW), where C " "is the number of channels, and kH and kW are the " "height and width of the kernel, and M is the number " "of feature maps. For more than 2 dimensions, the " "kernel shape will be (M x C/group x k1 x k2 x ... x kn), " "where (k1 x k2 x ... kn) is the dimension of the kernel. " "Optionally, if dimension denotation is in effect, " "the operation expects the weight tensor to arrive " "with the dimension denotation of [FILTER_OUT_CHANNEL, " "FILTER_IN_CHANNEL, FILTER_SPATIAL, FILTER_SPATIAL ...]. " "X.shape[1] == (W.shape[1] * group) == C " "(assuming zero based indices for the shape array). " "Or in other words FILTER_IN_CHANNEL should be equal to DATA_CHANNEL. ", "T"); schema.Input( 2, "B", "Optional 1D bias to be added to the convolution, has size of M.", "T", OpSchema::Optional); schema.Output( 0, "Y", "Output data tensor that contains the result of the " "convolution. The output dimensions are functions " "of the kernel size, stride size, and pad lengths.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.Attr( "kernel_shape", "The shape of the convolution kernel. If not present, should be inferred from input W.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "dilations", "dilation value along each spatial axis of the filter.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "strides", "Stride along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc2, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc2, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "group", "number of groups input channels and output channels are divided into.", AttributeProto::INT, static_cast(1)); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); convPoolShapeInference1(ctx, true, false, 0, 1); }); }; } ONNX_OPERATOR_SET_SCHEMA( Conv, 1, OpSchema().FillUsing(ConvOpSchemaGenerator_10("a filter"))); void convTransposeShapeInference1(InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); // we need at least two inputs to have a shape for this inference. if (!hasNInputShapes(ctx, 2)) { return; } int64_t group = getAttribute(ctx, "group", 1); auto input_shape = ctx.getInputType(0)->tensor_type().shape(); if (input_shape.dim_size() < 2) { return; // Input tensor should have at least two dimensions. } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); std::vector dilations; if (getRepeatedAttribute(ctx, "dilations", dilations)) { if (dilations.size() != n_input_dims) { return; } } else { dilations.assign(n_input_dims, 1); } std::vector strides; if (getRepeatedAttribute(ctx, "strides", strides)) { if (strides.size() != n_input_dims) { return; } } else { strides.assign(n_input_dims, 1); } std::vector kernel_shape; if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) { if (kernel_shape.size() != n_input_dims) { return; } } else { auto second_input_shape = ctx.getInputType(1)->tensor_type().shape(); for (int i = 2; i < second_input_shape.dim_size(); ++i) { if (!second_input_shape.dim(i).has_dim_value()) { return; } kernel_shape.push_back(second_input_shape.dim(i).dim_value()); } } std::vector effective_kernel_shape = kernel_shape; for (int i = 0; i < static_cast(kernel_shape.size()); i++) { // accounting for dilation, how big is the kernel in this dimension effective_kernel_shape[i] = (effective_kernel_shape[i] - 1) * dilations[i] + 1; } std::vector pads; if (getRepeatedAttribute(ctx, "pads", pads)) { if (pads.size() != n_input_dims * 2) { fail_shape_inference("Attribute pads has incorrect size"); } } else { pads.assign(n_input_dims * 2, 0); const auto* auto_pad_attr = ctx.getAttribute("auto_pad"); if ((nullptr != auto_pad_attr) && (auto_pad_attr->s() != "VALID")) { int input_dims_size = static_cast(n_input_dims); for (int i = 0; i < input_dims_size; ++i) { int64_t residual = 0; int64_t stride = strides[i]; if (stride > 1) { if (!input_shape.dim(2 + i).has_dim_value()) { continue; } residual = input_shape.dim(2 + i).dim_value(); while (residual >= stride) { residual -= stride; } } int64_t total_pad = residual == 0 ? effective_kernel_shape[i] - stride : effective_kernel_shape[i] - residual; if (total_pad < 0) total_pad = 0; int64_t half_pad_small = total_pad >> 1; int64_t half_pad_big = total_pad - half_pad_small; if (auto_pad_attr->s() == "SAME_UPPER") { pads[i] = half_pad_small; pads[i + input_dims_size] = half_pad_big; } else if (auto_pad_attr->s() == "SAME_LOWER") { pads[i] = half_pad_big; pads[i + input_dims_size] = half_pad_small; } } } } std::vector output_shape; bool output_shape_presented = true; if (getRepeatedAttribute(ctx, "output_shape", output_shape)) { if (output_shape.size() != n_input_dims) { return; } } else { output_shape_presented = false; } std::vector output_padding; if (getRepeatedAttribute(ctx, "output_padding", output_padding)) { if (output_padding.size() != n_input_dims) { // Added only to one side. return; } } else { output_padding.assign(n_input_dims, 0); } auto final_output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); *final_output_shape->add_dim() = input_shape.dim(0); *final_output_shape->add_dim() = ctx.getInputType(1)->tensor_type().shape().dim(1) * group; // channels should be the second dim of second input multiply // group. int size_of_output; if (output_shape_presented) { size_of_output = static_cast(output_shape.size()); for (int i = 0; i < size_of_output; ++i) { if (input_shape.dim(i + 2).has_dim_value()) { if (output_shape[i] < input_shape.dim(i + 2).dim_value()) { // TODO: throw exception? return; // output shape value cannot be smaller than the input shape // value } } final_output_shape->add_dim()->set_dim_value(output_shape[i]); } return; } else { size_of_output = input_shape.dim_size() - 2; for (int i = 0; i < size_of_output; ++i) { if (input_shape.dim(i + 2).has_dim_value()) { int64_t output_shape_dim = strides[i] * (input_shape.dim(i + 2).dim_value() - 1) + output_padding[i] + effective_kernel_shape[i] - pads[i] - pads[i + n_input_dims]; final_output_shape->add_dim()->set_dim_value(output_shape_dim); } else { final_output_shape->add_dim(); } } return; } } std::function ConvTransposeOpSchemaGenerator_10( const char* filter_desc) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( The convolution transpose operator consumes an input tensor and {filter_desc}, and computes the output. If the pads parameter is provided the shape of the output is calculated via the following equation: output_shape[i] = stride[i] * (input_size[i] - 1) + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - pads[start_i] - pads[end_i] output_shape can also be explicitly specified in which case pads values are auto generated using these equations: total_padding[i] = stride[i] * (input_size[i] - 1) + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i] If (auto_pads != SAME_UPPER): pads[start_i] = total_padding[i]/2; pads[end_i] = total_padding[i] - (total_padding[i]/2) Else: pads[start_i] = total_padding[i] - (total_padding[i]/2); pads[end_i] = (total_padding[i]/2). )DOC"; ReplaceAll(doc, "{filter_desc}", filter_desc);); schema.SetDoc(doc); schema.Input( 0, "X", "Input data tensor from previous layer; has size (N x C x H x W)" ", where N is the batch size, C is the number of channels, and" " H and W are the height and width. Note that this is for the 2D image. " "Otherwise the size is (N x C x D1 x D2 ... x Dn)", "T"); schema.Input( 1, "W", "The weight tensor that will be used in the " "convolutions; has size (C x M/group x kH x kW), where C " "is the number of channels, and kH and kW are the " "height and width of the kernel, and M is the number " "of feature maps. For more than 2 dimensions, the " "weight shape will be (C x M/group x k1 x k2 x ... x kn), " "where (k1 x k2 x ... x kn) is the dimension of the kernel. " "The number of channels in the output should be equal to W.shape[1] * group " "(assuming zero based indices of the shape array)", "T"); schema.Input( 2, "B", "Optional 1D bias to be added to the convolution, has size of M.", "T", OpSchema::Optional); schema.Output( 0, "Y", "Output data tensor that contains the result of the convolution. The " "output dimensions are functions of the kernel size, stride size, " "pad lengths and group count. " "The number of channels in the output should be equal to W.shape[1] * group " "(assuming zero based indices of the shape array)", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.Attr( "kernel_shape", "The shape of the convolution kernel. If not present, should be inferred from input W.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "output_shape", "The shape of the output can be explicitly set which will cause pads values to be auto generated. If output_shape is specified " "pads values are ignored. See doc for details for equations to generate pads", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "output_padding", "The zero-padding added to one side of the output." " This is also called adjs/adjustment in some frameworks.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "dilations", "dilation value along each spatial axis of the filter.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "strides", "Stride along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc2, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc2, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "group", "number of groups input channels and output channels are divided into.", AttributeProto::INT, static_cast(1)); schema.TypeAndShapeInferenceFunction( [](InferenceContext& ctx) { convTransposeShapeInference1(ctx); }); }; } ONNX_OPERATOR_SET_SCHEMA( ConvTranspose, 1, OpSchema().FillUsing(ConvTransposeOpSchemaGenerator_10("a filter"))); ONNX_OPERATOR_SET_SCHEMA( GlobalLpPool, 1, OpSchema() .SetDoc(GlobalLpPool_ver1_doc) .Attr( "p", "p value of the Lp norm used to pool over the input data, default is 2.0.", AttributeProto::FLOAT, 2.0f) .Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the width " "of the data. For non image case, the dimension are " "in the form of (N x C x D1 x D2 ... Dn), " "where N is the batch size.", "T") .Output( 0, "Y", "Output data tensor from pooling across the input " "tensor. Dimensions will be N x C x 1 x 1", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* BatchNormalization_ver1_doc = R"DOC( Carries out batch normalization as described in the paper https://arxiv.org/abs/1502.03167. Depending on the mode it is being run, there are multiple cases for the number of outputs, which we list below: Output case #1: Y, mean, var, saved_mean, saved_var (training mode) Output case #2: Y (test mode) )DOC"; ONNX_OPERATOR_SET_SCHEMA( BatchNormalization, 1, OpSchema() .NumOutputs({1, 5}) .SetDoc(BatchNormalization_ver1_doc) .Attr( "spatial", "If true, compute the mean and variance across all spatial elements " "If false, compute the mean and variance across per feature." "Default is 1.", AttributeProto::INT, static_cast(1)) .Attr( "is_test", "If set to nonzero, run spatial batch normalization in test mode, default is 0.", AttributeProto::INT, static_cast(0)) .Attr( "epsilon", "The epsilon value to use to avoid division by zero, default is 1e-5f.", AttributeProto::FLOAT, 1e-5f) .Attr( "momentum", "Factor used in computing the running mean and variance." "e.g., running_mean = running_mean * momentum + mean * (1 - momentum), default is 0.9f.", AttributeProto::FLOAT, 0.9f) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS) .Input(0, "X", "The input 4-dimensional tensor of shape NCHW.", "T") .Input( 1, "scale", "The scale as a 1-dimensional tensor of size C to be applied to the " "output.", "T") .Input( 2, "B", "The bias as a 1-dimensional tensor of size C to be applied to the " "output.", "T") .Input( 3, "mean", "The running mean (training) or the estimated mean (testing) " "as a 1-dimensional tensor of size C.", "T") .Input( 4, "var", "The running variance (training) or the estimated " "variance (testing) as a 1-dimensional tensor of size C.", "T") .Output( 0, "Y", "The output 4-dimensional tensor of the same shape as X.", "T") .Output( 1, "mean", "The running mean after the BatchNormalization operator. Must be in-place " "with the input mean. Should not be used for testing.", "T", OpSchema::Optional) .Output( 2, "var", "The running variance after the BatchNormalization operator. Must be " "in-place with the input var. Should not be used for testing.", "T", OpSchema::Optional) .Output( 3, "saved_mean", "Saved mean used during training to speed up gradient " "computation. Should not be used for testing.", "T", OpSchema::Optional) .Output( 4, "saved_var", "Saved variance used during training to speed up " "gradient computation. Should not be used for testing.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* InstanceNormalization_ver1_doc = R"DOC( Carries out instance normalization as described in the paper https://arxiv.org/abs/1607.08022. y = scale * (x - mean) / sqrt(variance + epsilon) + B, where mean and variance are computed per instance per channel. )DOC"; ONNX_OPERATOR_SET_SCHEMA( InstanceNormalization, 1, OpSchema() .SetDoc(InstanceNormalization_ver1_doc) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "epsilon", "The epsilon value to use to avoid division by zero, default is 1e-5f.", AttributeProto::FLOAT, 1e-5f) .Input(0, "input", "The input 4-dimensional tensor of shape NCHW.", "T") .Input( 1, "scale", "The input 1-dimensional scale tensor of size C.", "T") .Input(2, "B", "The input 1-dimensional bias tensor of size C.", "T") .Output( 0, "output", "The output 4-dimensional tensor of the same shape as input.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); static const char* Dropout_old_doc = R"DOC( Dropout takes one input data (Tensor) and produces two Tensor outputs, output (Tensor) and mask (Tensor). Depending on whether it is in test mode or not, the output Y will either be a random dropout, or a simple copy of the input. Note that our implementation of Dropout does scaling in the training phase, so during testing nothing needs to be done. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Dropout, 1, OpSchema() .SetDoc(Dropout_old_doc) .Attr( "ratio", "(float, default 0.5) the ratio of random dropout", AttributeProto::FLOAT, 0.5f) // This attribute was added via AllowConsumed API in OpSchema. // After removing the API, we're now using the Attr API to simulate the // old definition. .Attr( "consumed_inputs", "legacy optimization attribute.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "is_test", "(int, default 0) if nonzero, run dropout in test mode where " "the output is simply Y = X.", AttributeProto::INT, static_cast(0)) .Input(0, "data", "The input data as Tensor.", "T") .Output(0, "output", "The output.", "T") .Output( 1, "mask", "The output mask. If is_test is nonzero, this output is not filled.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.")); ONNX_OPERATOR_SET_SCHEMA( Dropout, 6, OpSchema() .SetDoc(Dropout_old_doc) .Attr( "ratio", "(float, default 0.5) the ratio of random dropout", AttributeProto::FLOAT, 0.5f) .Attr( "is_test", "(int, default 0) if nonzero, run dropout in test mode where " "the output is simply Y = X.", AttributeProto::INT, static_cast(0)) .Input(0, "data", "The input data as Tensor.", "T") .Output(0, "output", "The output.", "T") .Output( 1, "mask", "The output mask. If is_test is nonzero, this output is not filled.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Dropout_ver7_doc = R"DOC( Dropout takes one input data (Tensor) and produces two Tensor outputs, output (Tensor) and mask (Tensor). Depending on whether it is in test mode or not, the output Y will either be a random dropout, or a simple copy of the input. Note that our implementation of Dropout does scaling in the training phase, so during testing nothing needs to be done. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Dropout, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Dropout_ver7_doc) + GenerateOptionalArgumentsDoc())) .Attr( "ratio", "The ratio of random dropout", AttributeProto::FLOAT, 0.5f) .Input(0, "data", "The input data as Tensor.", "T") .Output(0, "output", "The output.", "T") .Output(1, "mask", "The output mask.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Dropout_ver10_doc = R"DOC( Dropout takes one input floating tensor and produces two tensor outputs, output (floating tensor) and mask (`Tensor`). Depending on whether it is in test mode or not, the output Y will either be a random dropout, or a simple copy of the input. Note that our implementation of Dropout does scaling in the training phase, so during testing nothing needs to be done. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Dropout, 10, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Dropout_ver10_doc) + GenerateOptionalArgumentsDoc())) .Attr( "ratio", "The ratio of random dropout", AttributeProto::FLOAT, 0.5f) .Input(0, "data", "The input data as Tensor.", "T") .Output(0, "output", "The output.", "T") .Output(1, "mask", "The output mask.", "T1", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrain output mask types to boolean tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateShapeAndTypeFromFirstInput(ctx); if (ctx.getNumOutputs() == 2) { updateOutputElemType(ctx, 1, TensorProto::BOOL); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 1); } } })); static const char* BatchNorm_ver6_doc = R"DOC( Carries out batch normalization as described in the paper https://arxiv.org/abs/1502.03167. Depending on the mode it is being run, there are multiple cases for the number of outputs, which we list below: Output case #1: Y, mean, var, saved_mean, saved_var (training mode) Output case #2: Y (test mode) )DOC"; ONNX_OPERATOR_SET_SCHEMA( BatchNormalization, 6, OpSchema() .NumOutputs({1, 5}) .SetDoc(BatchNorm_ver6_doc) .Attr( "spatial", "If true, compute the mean and variance across all spatial elements " "If false, compute the mean and variance across per feature." "Default is 1.", AttributeProto::INT, static_cast(1)) .Attr( "is_test", "If set to nonzero, run spatial batch normalization in test mode, default is 0.", AttributeProto::INT, static_cast(0)) .Attr( "epsilon", "The epsilon value to use to avoid division by zero, default is 1e-5f.", AttributeProto::FLOAT, 1e-5f) .Attr( "momentum", "Factor used in computing the running mean and variance." "e.g., running_mean = running_mean * momentum + mean * (1 - momentum), default is 0.9f.", AttributeProto::FLOAT, 0.9f) .Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size.", "T") .Input( 1, "scale", "The scale as a 1-dimensional tensor of size C to be applied to the " "output.", "T") .Input( 2, "B", "The bias as a 1-dimensional tensor of size C to be applied to the " "output.", "T") .Input( 3, "mean", "The running mean (training) or the estimated mean (testing) " "as a 1-dimensional tensor of size C.", "T") .Input( 4, "var", "The running variance (training) or the estimated " "variance (testing) as a 1-dimensional tensor of size C.", "T") .Output(0, "Y", "The output tensor of the same shape as X.", "T") .Output( 1, "mean", "The running mean after the BatchNormalization operator. Must be in-place " "with the input mean. Should not be used for testing.", "T", OpSchema::Optional) .Output( 2, "var", "The running variance after the BatchNormalization operator. Must be " "in-place with the input var. Should not be used for testing.", "T", OpSchema::Optional) .Output( 3, "saved_mean", "Saved mean used during training to speed up gradient " "computation. Should not be used for testing.", "T", OpSchema::Optional) .Output( 4, "saved_var", "Saved variance used during training to speed up " "gradient computation. Should not be used for testing.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateShapeAndTypeFromFirstInput(ctx); // TODO in training mode, it may be possible to infer some of // the other outputs as well. })); static const char* Flatten_ver1_doc = R"DOC( Flattens the input tensor into a 2D matrix. If input tensor has shape (d_0, d_1, ... d_n) then the output will have shape (d_0 X d_1 ... d_(axis-1), d_axis X d_(axis+1) ... X dn). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Flatten, 1, OpSchema() .SetDoc(Flatten_ver1_doc) .Input(0, "input", "A tensor of rank >= axis.", "T") .Output( 0, "output", "A 2D tensor with the contents of the input tensor, " "with input dimensions up to axis flattened to the outer dimension " "of the output and remaining input dimensions flattened into the inner " "dimension of the output.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .Attr( "axis", "Indicate up to which input dimensions " "(exclusive) should be flattened to the outer dimension of the output. " "The value for axis must be in the range [0, R], where R is the rank of the input tensor. " "When axis = 0, the shape of the output tensor is (1, (d_0 X d_1 ... d_n), " "where the shape of the input tensor is (d_0, d_1, ... d_n). ", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); int rank = static_cast(input_shape.dim_size()); int axis = static_cast(getAttribute(ctx, "axis", 1)); if (axis > rank || axis < 0) { fail_shape_inference( "Invalid value(", axis, ") for attribute 'axis'"); } // TODO: is the operation defined for input-rank < 2? updateOutputShape( ctx, 0, {multiplyDims(input_shape, 0, axis), multiplyDims(input_shape, axis, rank)}); })); static const char* Flatten_ver9_doc = R"DOC( Flattens the input tensor into a 2D matrix. If input tensor has shape (d_0, d_1, ... d_n) then the output will have shape (d_0 X d_1 ... d_(axis-1), d_axis X d_(axis+1) ... X dn). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Flatten, 9, OpSchema() .SetDoc(Flatten_ver9_doc) .Input(0, "input", "A tensor of rank >= axis.", "T") .Output( 0, "output", "A 2D tensor with the contents of the input tensor, " "with input dimensions up to axis flattened to the outer dimension " "of the output and remaining input dimensions flattened into the inner " "dimension of the output.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output to all tensor types.") .Attr( "axis", "Indicate up to which input dimensions " "(exclusive) should be flattened to the outer dimension of the output. " "The value for axis must be in the range [0, R], where R is the rank of the input tensor. " "When axis = 0, the shape of the output tensor is (1, (d_0 X d_1 ... d_n), " "where the shape of the input tensor is (d_0, d_1, ... d_n). ", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); int rank = static_cast(input_shape.dim_size()); int axis = static_cast(getAttribute(ctx, "axis", 1)); if (axis > rank || axis < 0) { fail_shape_inference( "Invalid value(", axis, ") for attribute 'axis'"); } // TODO: is the operation defined for input-rank < 2? updateOutputShape( ctx, 0, {multiplyDims(input_shape, 0, axis), multiplyDims(input_shape, axis, rank)}); })); static const char* BatchNormalization_ver7_doc = R"DOC( Carries out batch normalization as described in the paper https://arxiv.org/abs/1502.03167. Depending on the mode it is being run, there are multiple cases for the number of outputs, which we list below: Output case #1: Y, mean, var, saved_mean, saved_var (training mode) Output case #2: Y (test mode) )DOC"; ONNX_OPERATOR_SET_SCHEMA( BatchNormalization, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(BatchNormalization_ver7_doc) + GenerateOptionalArgumentsDoc())) .NumOutputs({1, 5}) .Attr( "spatial", "If true, compute the mean and variance across per activation. " "If false, compute the mean and variance across per feature over " "each mini-batch.", AttributeProto::INT, static_cast(1)) .Attr( "epsilon", "The epsilon value to use to avoid division by zero.", AttributeProto::FLOAT, 1e-5f) .Attr( "momentum", "Factor used in computing the running mean and variance." "e.g., running_mean = running_mean * momentum + mean * (1 - momentum).", AttributeProto::FLOAT, 0.9f) .Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size.", "T") .Input( 1, "scale", "If spatial is true, the dimension of scale is (C). " "If spatial is false, the dimensions of scale are " "(C x D1 x ... x Dn)", "T") .Input( 2, "B", "If spatial is true, the dimension of bias is (C). " "If spatial is false, the dimensions of bias are " "(C x D1 x ... x Dn)", "T") .Input( 3, "mean", "If spatial is true, the dimension of the running mean " "(training) or the estimated mean (testing) is (C). " "If spatial is false, the dimensions of the running mean " "(training) or the estimated mean (testing) are (C x D1 x ... x Dn).", "T") .Input( 4, "var", "If spatial is true, the dimension of the running variance" "(training) or the estimated variance (testing) is (C). " "If spatial is false, the dimensions of the running variance" "(training) or the estimated variance (testing) are (C x D1 x ... x Dn).", "T") .Output(0, "Y", "The output tensor of the same shape as X", "T") .Output( 1, "mean", "The running mean after the BatchNormalization operator.", "T", OpSchema::Optional) .Output( 2, "var", "The running variance after the BatchNormalization operator.", "T", OpSchema::Optional) .Output( 3, "saved_mean", "Saved mean used during training to speed up gradient " "computation.", "T", OpSchema::Optional) .Output( 4, "saved_var", "Saved variance used during training to speed up " "gradient computation.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateShapeAndTypeFromFirstInput(ctx); // TODO in training mode, it may be possible to infer some of // the other outputs as well. })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/nn/defs.cc0000664000000000000000000025426313655345213014772 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include #include #include "onnx/defs/function.h" #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { const char* pads_doc = "Padding for the beginning and ending along each spatial axis, it can take any value greater " "than or equal to 0. The value represent the number of pixels added to the beginning " "and end part of the corresponding axis. `pads` format should be as follow " "[x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of pixels " "added at the beginning of axis `i` and xi_end, the number of pixels added at " "the end of axis `i`. This attribute cannot be used simultaneously with " "auto_pad attribute. If not present, the padding defaults to 0 along start and end of each spatial axis."; const char* auto_pad_doc = "auto_pad must be either NOTSET, SAME_UPPER, SAME_LOWER or VALID. Where " "default value is NOTSET, which means explicit padding is used. " "SAME_UPPER or SAME_LOWER mean pad the input so that the output spatial size match the input." "In case of odd number add the extra padding at the end for SAME_UPPER and at the " "beginning for SAME_LOWER. VALID mean no padding."; void convPoolShapeInference( InferenceContext& ctx, bool use_dilation, bool require_kernel_shape, int input1Idx, int input2Idx) { // we need the first input shape for this inference. if (!hasInputShape(ctx, input1Idx)) { return; } // if kernel shape is an input (and not attribute) // we need the shape of the second input. if (!require_kernel_shape && !hasInputShape(ctx, input2Idx)) { return; } auto input_shape = ctx.getInputType(input1Idx)->tensor_type().shape(); if (input_shape.dim_size() < 2) { fail_shape_inference("Input tensor must have atleast 2 dimensions"); } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); // Only MaxPool and Conv support dilation. For // simplicity of the code, we just treat the rest of them as having all-1s // dilation. std::vector dilations; if (use_dilation && getRepeatedAttribute(ctx, "dilations", dilations)) { if (dilations.size() != n_input_dims) { fail_shape_inference("Attribute dilations has incorrect size"); } } else { dilations.assign(n_input_dims, 1); } std::vector strides; if (getRepeatedAttribute(ctx, "strides", strides)) { if (strides.size() != n_input_dims) { fail_shape_inference("Attribute strides has incorrect size"); } } else { strides.assign(n_input_dims, 1); } std::vector kernel_shape; if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) { if (kernel_shape.size() != n_input_dims) { fail_shape_inference("Attribute kernel_shape has incorrect size"); } } else if (require_kernel_shape) { fail_shape_inference("Attribute kernel_shape must be specified"); } else { auto second_input_shape = ctx.getInputType(input2Idx)->tensor_type().shape(); for (int i = 2; i < second_input_shape.dim_size(); ++i) { if (!second_input_shape.dim(i).has_dim_value()) { return; } kernel_shape.push_back(second_input_shape.dim(i).dim_value()); } } std::vector effective_kernel_shape = kernel_shape; for (int i = 0; i < static_cast(kernel_shape.size()); i++) { // accounting for dilation, how big is the kernel in this dimension effective_kernel_shape[i] = (effective_kernel_shape[i] - 1) * dilations[i] + 1; } std::vector pads; if (getRepeatedAttribute(ctx, "pads", pads)) { if (pads.size() != n_input_dims * 2) { fail_shape_inference("Attribute pads has incorrect size"); } } else { pads.assign(n_input_dims * 2, 0); const auto* auto_pad_attr = ctx.getAttribute("auto_pad"); if ((nullptr != auto_pad_attr) && (auto_pad_attr->s() != "VALID")) { int input_dims_size = static_cast(n_input_dims); for (int i = 0; i < input_dims_size; ++i) { int64_t residual = 0; int64_t stride = strides[i]; if (stride > 1) { if (!input_shape.dim(2 + i).has_dim_value()) { continue; } residual = input_shape.dim(2 + i).dim_value(); while (residual >= stride) { residual -= stride; } } int64_t total_pad = residual == 0 ? effective_kernel_shape[i] - stride : effective_kernel_shape[i] - residual; if (total_pad < 0) total_pad = 0; int64_t half_pad_small = total_pad >> 1; int64_t half_pad_big = total_pad - half_pad_small; if (auto_pad_attr->s() == "SAME_UPPER") { pads[i] = half_pad_small; pads[i + input_dims_size] = half_pad_big; } else if (auto_pad_attr->s() == "SAME_LOWER") { pads[i] = half_pad_big; pads[i + input_dims_size] = half_pad_small; } } } } auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); if (require_kernel_shape) { // add the first two dimensions from the input. *output_shape->add_dim() = input_shape.dim(0); *output_shape->add_dim() = input_shape.dim(1); } else { *output_shape->add_dim() = input_shape.dim(0); auto& second_input_shape = getInputShape(ctx, input2Idx); if (second_input_shape.dim_size() < 1) { fail_shape_inference("Second input tensor has wrong dimension"); } *output_shape->add_dim() = second_input_shape.dim(0); } int kernel_shape_size = static_cast(kernel_shape.size()); for (int i = 0; i < kernel_shape_size; ++i) { auto newdim = output_shape->add_dim(); if (!input_shape.dim(2 + i).has_dim_value()) { continue; } // how big is the input, including padding int64_t effective_input_size = input_shape.dim(2 + i).dim_value(); effective_input_size += pads[i]; effective_input_size += pads[i + kernel_shape_size]; // default is floor mode .i.e. ceil_mode is set to 0 auto ceil_mode = getAttribute(ctx, "ceil_mode", 0); // how many times we can move the kernel from it's initial position, based // on the stride int64_t strided_kernel_positions; if (ceil_mode == 1) strided_kernel_positions = (int64_t)(std::ceil( (effective_input_size - effective_kernel_shape[i]) / float(strides[i]))); else strided_kernel_positions = (effective_input_size - effective_kernel_shape[i]) / strides[i]; // add in the initial position newdim->set_dim_value(1 + strided_kernel_positions); } if (ctx.getNumOutputs() > 1) { // MaxPool with two outputs case. auto second_output_shape = ctx.getOutputType(1)->mutable_tensor_type()->mutable_shape(); second_output_shape->CopyFrom(*output_shape); } } std::vector GetSupportedDataTypesForPoolingOps(bool supports8bit) { if (supports8bit) { return {"tensor(float16)", "tensor(float)", "tensor(double)", "tensor(int8)", "tensor(uint8)"}; } return {"tensor(float16)", "tensor(float)", "tensor(double)"}; } std::function PoolOpSchemaGenerator( const char* name, const char* opName, const char* additionalDescription, bool use_dilation, bool supports8bit = false) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( {name} consumes an input tensor X and applies {opName} pooling across the tensor according to kernel sizes, stride sizes, and pad lengths. {opName} pooling consisting of computing the {opName} on all values of a subset of the input tensor according to the kernel size and downsampling the data into the output tensor Y for further processing. The output spatial shape will be following: ``` output_spatial_shape[i] = floor((input_spatial_shape[i] + pad_shape[i] - {kernelSpatialShape}) / strides_spatial_shape[i] + 1) ``` or ``` output_spatial_shape[i] = ceil((input_spatial_shape[i] + pad_shape[i] - {kernelSpatialShape}) / strides_spatial_shape[i] + 1) ``` if ceil_mode is enabled ``` * pad_shape[i] is sum of pads along axis i ``` `auto_pad` is a DEPRECATED attribute. If you are using them currently, the output spatial shape will be following: ``` VALID: output_spatial_shape[i] = ceil((input_spatial_shape[i] - {kernelSpatialShape} + 1) / strides_spatial_shape[i]) SAME_UPPER or SAME_LOWER: output_spatial_shape[i] = ceil(input_spatial_shape[i] / strides_spatial_shape[i]) ``` And pad shape will be following if `SAME_UPPER` or `SAME_LOWER`: ``` pad_shape[i] = (output_spatial_shape[i] - 1) * strides_spatial_shape[i] + {kernelSpatialShape} - input_spatial_shape[i] ``` {additionalDescription} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll(doc, "{opName}", opName); ReplaceAll(doc, "{additionalDescription}", additionalDescription); ReplaceAll( doc, "{kernelSpatialShape}", use_dilation ? "((kernel_spatial_shape[i] - 1) * dilations[i] + 1)" : "kernel_spatial_shape[i]");); schema.SetDoc(doc); schema.Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS); schema.Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "ceil_mode", "Whether to use ceil or floor (default) to compute the output shape.", AttributeProto::INT, static_cast(0)); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size. Optionally, if dimension denotation is " "in effect, the operation expects the input " "data tensor to arrive with the dimension denotation " "of [DATA_BATCH, DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T"); schema.Output( 0, "Y", "Output data tensor from average or max pooling across " "the input tensor. Dimensions will vary based " "on various kernel, stride, and pad sizes. Floor value of " "the dimension is used", "T"); schema.TypeConstraint( "T", GetSupportedDataTypesForPoolingOps(supports8bit), supports8bit ? "Constrain input and output types to float and 8 bit tensors." : "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([use_dilation](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (ctx.getNumOutputs() > 1) { // MaxPool with two outputs case. auto output_type = ctx.getOutputType(1); if (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET) { output_type->mutable_tensor_type()->set_elem_type(TensorProto::INT64); } } convPoolShapeInference(ctx, use_dilation, true, 0, 1); }); }; } ONNX_OPERATOR_SET_SCHEMA( AveragePool, 11, OpSchema() .FillUsing(PoolOpSchemaGenerator( "AveragePool", "average", "The output of each pooling window is divided by the number of elements (exclude pad when attribute count_include_pad is zero).", false, false)) .Attr( "count_include_pad", "Whether include pad pixels when calculating values for the edges. Default is 0, doesn't count include pad.", AttributeProto::INT, static_cast(0))); ONNX_OPERATOR_SET_SCHEMA( MaxPool, 12, OpSchema() .FillUsing(PoolOpSchemaGenerator( "MaxPool", "max", "The output of each pooling window is maximum number of elements exclude pad. ", true, true)) .Attr( "storage_order", "The storage order of the tensor. 0 is row major, and 1 is column major.", AttributeProto::INT, static_cast(0)) .Attr( "dilations", "Dilation value along each spatial axis of filter. If not present, the dilation defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Output( 1, "Indices", "Indices tensor from max pooling across the input tensor. " "The dimensions of indices are the same as output tensor. " "The values in indices of are the indices of the selected values during pooling. " "The indices are computed as flatten 1-D tensor, " "and the indices do not consider padding. " "So the values in indices are in [0, N x C x D1 x ... x Dn).", "I", OpSchema::Optional) .TypeConstraint( "I", {"tensor(int64)"}, "Constrain index tensor to int64")); void maxUnpoolShapeInference(InferenceContext& ctx) { // we need at least two inputs to have a shape for this inference. if (ctx.getNumInputs() != 2 && ctx.getNumInputs() != 3) { fail_type_inference("MaxUnpool op must have either two or three inputs."); } propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasInputShape(ctx, 0)) { return; // If first input does not have shape, we cannot infer much. } auto input_shape = ctx.getInputType(0)->tensor_type().shape(); if (input_shape.dim_size() < 2) { fail_shape_inference("Input tensor X must have atleast 2 dimensions."); } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); std::vector pads; if (getRepeatedAttribute(ctx, "pads", pads)) { if (pads.size() != n_input_dims * 2) { fail_shape_inference("Attribute pads has incorrect size."); } } else { pads.assign(n_input_dims * 2, 0); } std::vector strides; if (getRepeatedAttribute(ctx, "strides", strides)) { if (strides.size() != n_input_dims) { fail_shape_inference("Attribute strides has incorrect size."); } } else { strides.assign(n_input_dims, 1); } std::vector kernel_shape; if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) { if (kernel_shape.size() != n_input_dims) { fail_shape_inference("Attribute kernel_shape has incorrect size."); } } else { fail_shape_inference("Attribute kernel_shape must be specified."); } if (ctx.getNumInputs() == 3) { // If the third input, output_size, is specified, then use that instead // of inferring shape from inputs. if (hasInputShape(ctx, 2)) { auto& output_shape = getInputShape(ctx, 2); if (output_shape.dim_size() != 1) { fail_type_inference("'output_shape' must be rank 1 tensor."); } if (output_shape.dim((int)0).has_dim_value() && static_cast(output_shape.dim((int)0).dim_value()) != input_shape.dim_size()) { fail_shape_inference( "'output_shape' must have same number of elements as the shape of input tensor X."); } } return; // 'output_shape' is specified as input. Actual shape will be // determined at runtime. } auto final_output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); *final_output_shape->add_dim() = input_shape.dim(0); *final_output_shape->add_dim() = ctx.getInputType(1)->tensor_type().shape().dim( 1); // channels should be the second dim of second input. int kernel_shape_size = static_cast(kernel_shape.size()); for (int i = 0; i < kernel_shape_size; ++i) { auto newdim = final_output_shape->add_dim(); if (!input_shape.dim(2 + i).has_dim_value()) { continue; } int64_t newdim_value = strides[i] * (input_shape.dim(2 + i).dim_value() - 1); newdim_value += kernel_shape[i]; newdim_value -= pads[i]; newdim_value -= pads[i + kernel_shape_size]; // add in the initial position newdim->set_dim_value(newdim_value); } } static const char* MaxUnpool_ver9_doc = R"DOC( MaxUnpool essentially computes the partial inverse of the MaxPool op. The input information to this op is typically the the output information from a MaxPool op. The first input tensor X is the tensor that needs to be unpooled, which is typically the pooled tensor (first output) from MaxPool. The second input tensor, I, contains the indices to the (locally maximal) elements corrsponding to the elements in the first input tensor X. Input tensor I is typically the second output of the MaxPool op. The third (optional) input is a tensor that specifies the output size of the unpooling operation. MaxUnpool is intended to do 'partial' inverse of the MaxPool op. 'Partial' because all the non-maximal values from the original input to MaxPool are set to zero in the output of the MaxUnpool op. Pooling the result of an unpooling operation should give back the original input to the unpooling op. MaxUnpool can produce the same output size for several input sizes, which makes unpooling op ambiguous. The third input argument, output_size, is meant to disambiguate the op and produce output tensor of known/predictable size. In addition to the inputs, MaxUnpool takes three attributes, namely kernel_shape, strides, and pads, which define the exact unpooling op. The attributes typically have the same values as the corrsponding pooling op that the unpooling op is trying to invert. )DOC"; ONNX_OPERATOR_SET_SCHEMA( MaxUnpool, 11, OpSchema() .SetDoc(MaxUnpool_ver9_doc) .Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS) .Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr("pads", pads_doc, AttributeProto::INTS, OPTIONAL_VALUE) .Input( 0, "X", "Input data tensor that has to be unpooled. " "This tensor is typically the first output of the MaxPool op." "Dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non-image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size. Optionally, if dimension denotation is " "in effect, the operation expects the input " "data tensor to arrive with the dimension denotation " "of [DATA_BATCH, DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T1") .Input( 1, "I", "Input data tensor containing the indices corresponding to " "elements in the first input tensor X." "This tensor is typically the second output of the MaxPool op." "Dimensions must be the same as input tensor X. " "The indices are linear, i.e. computed considering the tensor as flattened 1-D tensor, " "assuming row-major storage. Also, the linear indices should not consider padding. " "So the values in indices are in the range [0, N x C x D1 x ... x Dn).", "T2") .Input( 2, "output_shape", "The shape of the output can be explicitly set which will cause pads values to be auto generated. If 'output_shape' is specified, " "'pads' values are ignored.", "T2", OpSchema::Optional) .Output( 0, "output", "Output data tensor that contains the result of the unpooling.", "T1") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "T2", {"tensor(int64)"}, "Constrain index tensor to int64") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { maxUnpoolShapeInference(ctx); })); std::function LpPoolOpSchemaGenerator(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( {name} consumes an input tensor X and applies Lp pooling across the tensor according to kernel sizes, stride sizes, and pad lengths. Lp pooling consisting of computing the Lp norm on all values of a subset of the input tensor according to the kernel size and downsampling the data into the output tensor Y for further processing.)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc); schema.Attr( "kernel_shape", "The size of the kernel along each axis.", AttributeProto::INTS); schema.Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "p", "p value of the Lp norm used to pool over the input data.", AttributeProto::INT, static_cast(2)); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the " "batch size.", "T"); schema.Output( 0, "Y", "Output data tensor from Lp pooling across the input " "tensor. Dimensions will vary based on various kernel, stride, and pad " "sizes.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); convPoolShapeInference(ctx, false, true, 0, 1); }); }; } ONNX_OPERATOR_SET_SCHEMA( LpPool, 11, OpSchema().FillUsing(LpPoolOpSchemaGenerator("LpPool"))); // For ROI pool operations. void roiPoolTypeShapeInference(InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); // rois is the second input. if (!hasNInputShapes(ctx, 2)) { return; } auto input_shape = ctx.getInputType(0)->tensor_type().shape(); auto rios_shape = ctx.getInputType(1)->tensor_type().shape(); if (input_shape.dim_size() < 2) { fail_shape_inference("Input tensor must have at least 2 dimensions"); } if (rios_shape.dim_size() != 2) { fail_shape_inference("RoIs tensor must have 2 dimensions"); } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); std::vector pooled_shape; if (getRepeatedAttribute(ctx, "pooled_shape", pooled_shape)) { if (pooled_shape.size() != n_input_dims) { fail_shape_inference("Attribute pooled_shape has incorrect length"); } } else { fail_shape_inference("Attribute pooled_shape must be specified"); } // (num_rois, channels, pooled_shape[0], pooled_shape[1]) auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); *output_shape->add_dim() = rios_shape.dim(0); *output_shape->add_dim() = input_shape.dim(1); output_shape->add_dim()->set_dim_value(pooled_shape[0]); output_shape->add_dim()->set_dim_value(pooled_shape[1]); } std::function RoiPoolOpSchemaGenerator(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( ROI {name} pool consumes an input tensor X and region of interests (RoIs) to apply {name} pooling across each RoI, to produce output 4-D tensor of shape (num_rois, channels, pooled_shape[0], pooled_shape[1]).)DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc); schema.Attr( "pooled_shape", "ROI pool output shape (height, width).", AttributeProto::INTS); schema.Attr( "spatial_scale", "Multiplicative spatial scale factor to translate ROI coordinates from their input scale to the scale used when pooling.", AttributeProto::FLOAT, 1.f); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data.", "T"); schema.Input( 1, "rois", "RoIs (Regions of Interest) to pool over. Should " "be a 2-D tensor of shape (num_rois, 5) given as " "[[batch_id, x1, y1, x2, y2], ...].", "T"); schema.Output( 0, "Y", "RoI pooled output 4-D tensor of shape (num_rois, channels, pooled_shape[0], pooled_shape[1]).", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction( [](InferenceContext& ctx) { roiPoolTypeShapeInference(ctx); }); }; } ONNX_OPERATOR_SET_SCHEMA( MaxRoiPool, 1, OpSchema().FillUsing(RoiPoolOpSchemaGenerator("max"))); std::function ConvOpSchemaGenerator(const char* filter_desc) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( The convolution operator consumes an input tensor and {filter_desc}, and computes the output.)DOC"; ReplaceAll(doc, "{filter_desc}", filter_desc);); schema.SetDoc(doc); schema.Input( 0, "X", "Input data tensor from previous layer; " "has size (N x C x H x W), where N is the batch size, " "C is the number of channels, and H and W are the " "height and width. Note that this is for the 2D image. " "Otherwise the size is (N x C x D1 x D2 ... x Dn). " "Optionally, if dimension denotation is " "in effect, the operation expects input data tensor " "to arrive with the dimension denotation of [DATA_BATCH, " "DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T"); schema.Input( 1, "W", "The weight tensor that will be used in the " "convolutions; has size (M x C/group x kH x kW), where C " "is the number of channels, and kH and kW are the " "height and width of the kernel, and M is the number " "of feature maps. For more than 2 dimensions, the " "kernel shape will be (M x C/group x k1 x k2 x ... x kn), " "where (k1 x k2 x ... kn) is the dimension of the kernel. " "Optionally, if dimension denotation is in effect, " "the operation expects the weight tensor to arrive " "with the dimension denotation of [FILTER_OUT_CHANNEL, " "FILTER_IN_CHANNEL, FILTER_SPATIAL, FILTER_SPATIAL ...]. " "X.shape[1] == (W.shape[1] * group) == C " "(assuming zero based indices for the shape array). " "Or in other words FILTER_IN_CHANNEL should be equal to DATA_CHANNEL. ", "T"); schema.Input( 2, "B", "Optional 1D bias to be added to the convolution, has size of M.", "T", OpSchema::Optional); schema.Output( 0, "Y", "Output data tensor that contains the result of the " "convolution. The output dimensions are functions " "of the kernel size, stride size, and pad lengths.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.Attr( "kernel_shape", "The shape of the convolution kernel. If not present, should be inferred from input W.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "dilations", "dilation value along each spatial axis of the filter. If not present, the dilation defaults is 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults is 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "group", "number of groups input channels and output channels are divided into.", AttributeProto::INT, static_cast(1)); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); convPoolShapeInference(ctx, true, false, 0, 1); }); }; } ONNX_OPERATOR_SET_SCHEMA( Conv, 11, OpSchema().FillUsing(ConvOpSchemaGenerator("a filter"))); static const char* QLinearConv_ver10_doc = R"DOC( The convolution operator consumes a quantized input tensor, its scale and zero point, a quantized filter, its scale and zero point, and output's scale and zero point, and computes the quantized output. Each scale and zero-point pair must have same shape. It means they must be either scalars (per tensor) or 1-D tensors (per output channel). Each input or output and its related zero point must have same type. When bias is present it must be quantized using scale = input scale * weight scale and zero point as 0. )DOC"; ONNX_OPERATOR_SET_SCHEMA( QLinearConv, 10, OpSchema() .SetDoc(QLinearConv_ver10_doc) .Input( 0, "x", "Input data tensor from previous layer; " "has size (N x C x H x W), where N is the batch size, " "C is the number of channels, and H and W are the " "height and width. Note that this is for the 2D image. " "Otherwise the size is (N x C x D1 x D2 ... x Dn). " "Optionally, if dimension denotation is " "in effect, the operation expects input data tensor " "to arrive with the dimension denotation of [DATA_BATCH, " "DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T1") .Input( 1, "x_scale", "Scale tensor for input 'x'. It's a scalar, which means a per-tensor/layer quantization.", "tensor(float)") .Input( 2, "x_zero_point", "Zero point tensor for input 'x'. It's a scalar, which means a per-tensor/layer quantization.", "T1") .Input( 3, "w", "The weight tensor that will be used in the " "convolutions; has size (M x C/group x kH x kW), where C " "is the number of channels, and kH and kW are the " "height and width of the kernel, and M is the number " "of feature maps. For more than 2 dimensions, the " "kernel shape will be (M x C/group x k1 x k2 x ... x kn), " "where (k1 x k2 x ... kn) is the dimension of the kernel. " "Optionally, if dimension denotation is in effect, " "the operation expects the weight tensor to arrive " "with the dimension denotation of [FILTER_OUT_CHANNEL, " "FILTER_IN_CHANNEL, FILTER_SPATIAL, FILTER_SPATIAL ...]. " "X.shape[1] == (W.shape[1] * group) == C " "(assuming zero based indices for the shape array). " "Or in other words FILTER_IN_CHANNEL should be equal to DATA_CHANNEL. ", "T2") .Input( 4, "w_scale", "Scale tensor for input 'w'. It could be a scalar or a 1-D tensor, which means a per-tensor/layer or per output channel quantization. If it's a 1-D tensor, its number of elements should be equal to the number of output channels (M).", "tensor(float)") .Input( 5, "w_zero_point", "Zero point tensor for input 'w'. It could be a scalar or a 1-D tensor, which means a per-tensor/layer or per output channel quantization. If it's a 1-D tensor, its number of elements should be equal to the number of output channels (M).", "T2") .Input( 6, "y_scale", "Scale tensor for output 'y'. It's a scalar, which means a per-tensor/layer quantization.", "tensor(float)") .Input( 7, "y_zero_point", "Zero point tensor for output 'y'. It's a scalar, which means a per-tensor/layer quantization.", "T3") .Input( 8, "B", "Optional 1D bias to be added to the convolution, has size of M. " "Bias must be quantized using scale = x_scale * w_scale and zero_point = 0", "T4", OpSchema::Optional) .Output( 0, "y", "Output data tensor that contains the result of the " "convolution. The output dimensions are functions " "of the kernel size, stride size, and pad lengths.", "T3") .TypeConstraint( "T1", {"tensor(int8)", "tensor(uint8)"}, "Constrain input type to 8-bit integer tensor.") .TypeConstraint( "T2", {"tensor(int8)", "tensor(uint8)"}, "Constrain filter type to 8-bit integer tensor.") .TypeConstraint( "T3", {"tensor(int8)", "tensor(uint8)"}, "Constrain output type to 8-bit integer tensor.") .TypeConstraint( "T4", {"tensor(int32)"}, "Constrain bias type to 32-bit integer tensor.") .Attr( "auto_pad", auto_pad_doc, AttributeProto::STRING, std::string("NOTSET")) .Attr( "kernel_shape", "The shape of the convolution kernel. If not present, should be inferred from input 'w'.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "dilations", "dilation value along each spatial axis of the filter. If not present, the dilation defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "pads", "Padding for the beginning and ending along each spatial axis, it can take any value greater than or equal to 0." "The value represent the number of pixels added to the beginning and end part of the corresponding axis." "`pads` format should be as follow [x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of" "pixels added at the beginning of axis `i` and xi_end, the number of pixels added at the end of axis `i`." "This attribute cannot be used simultaneously with auto_pad attribute. If not present, the padding defaults" "to 0 along start and end of each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "group", "number of groups input channels and output channels are divided into. default is 1.", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto x_type = ctx.getInputType(0); auto w_type = ctx.getInputType(3); if (nullptr == x_type || nullptr == w_type || x_type->value_case() != TypeProto::kTensorType || w_type->value_case() != TypeProto::kTensorType) { fail_type_inference("inputs are expected to have tensor type."); } auto x_zero_point_type = ctx.getInputType(2); if (nullptr == x_zero_point_type || x_zero_point_type->tensor_type().elem_type() != x_type->tensor_type().elem_type()) { fail_type_inference( "input and zero_point pair is expected to have be same type."); } auto w_zero_point_type = ctx.getInputType(5); if (nullptr == w_zero_point_type || w_zero_point_type->tensor_type().elem_type() != w_type->tensor_type().elem_type()) { fail_type_inference( "weight and zero_point pair is expected to have same type."); } propagateElemTypeFromInputToOutput(ctx, 7, 0); convPoolShapeInference(ctx, true, false, 0, 3); })); static const char* ConvInteger_ver10_doc = R"DOC( The integer convolution operator consumes an input tensor, its zero-point, a filter, and its zero-point, and computes the output. The production MUST never overflow. The accumulation may overflow if and only if in 32 bits. )DOC"; ONNX_OPERATOR_SET_SCHEMA( ConvInteger, 10, OpSchema() .SetDoc(ConvInteger_ver10_doc) .Input( 0, "x", "Input data tensor from previous layer; " "has size (N x C x H x W), where N is the batch size, " "C is the number of channels, and H and W are the " "height and width. Note that this is for the 2D image. " "Otherwise the size is (N x C x D1 x D2 ... x Dn). " "Optionally, if dimension denotation is " "in effect, the operation expects input data tensor " "to arrive with the dimension denotation of [DATA_BATCH, " "DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T1") .Input( 1, "w", "The weight tensor that will be used in the " "convolutions; has size (M x C/group x kH x kW), where C " "is the number of channels, and kH and kW are the " "height and width of the kernel, and M is the number " "of feature maps. For more than 2 dimensions, the " "kernel shape will be (M x C/group x k1 x k2 x ... x kn), " "where (k1 x k2 x ... kn) is the dimension of the kernel. " "Optionally, if dimension denotation is in effect, " "the operation expects the weight tensor to arrive " "with the dimension denotation of [FILTER_OUT_CHANNEL, " "FILTER_IN_CHANNEL, FILTER_SPATIAL, FILTER_SPATIAL ...]. " "X.shape[1] == (W.shape[1] * group) == C " "(assuming zero based indices for the shape array). " "Or in other words FILTER_IN_CHANNEL should be equal to DATA_CHANNEL. ", "T2") .Input( 2, "x_zero_point", "Zero point tensor for input 'x'. It's optional and default value is 0. It's a scalar, which means a per-tensor/layer quantization.", "T1", OpSchema::Optional) .Input( 3, "w_zero_point", "Zero point tensor for input 'w'. It's optional and default value is 0. It could be a scalar or a 1-D tensor, " "which means a per-tensor/layer or per output channel quantization. If it's a 1-D tensor, its number " "of elements should be equal to the number of output channels (M)", "T2", OpSchema::Optional) .Output( 0, "y", "Output data tensor that contains the result of the " "convolution. The output dimensions are functions " "of the kernel size, stride size, and pad lengths.", "T3") .TypeConstraint( "T1", {"tensor(int8)", "tensor(uint8)"}, "Constrain input x and its zero point data type to 8-bit integer tensor.") .TypeConstraint( "T2", {"tensor(int8)", "tensor(uint8)"}, "Constrain input w and its zero point data type to 8-bit integer tensor.") .TypeConstraint( "T3", {"tensor(int32)"}, "Constrain output y data type to 32-bit integer tensor.") .Attr( "auto_pad", auto_pad_doc, AttributeProto::STRING, std::string("NOTSET")) .Attr( "kernel_shape", "The shape of the convolution kernel. If not present, should be inferred from input 'w'.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "dilations", "dilation value along each spatial axis of the filter. If not present, the dilation defaults to 1 along each axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults to 1 along each axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "pads", "Padding for the beginning and ending along each spatial axis, it can take any value greater than or equal to 0." "The value represent the number of pixels added to the beginning and end part of the corresponding axis." "`pads` format should be as follow [x1_begin, x2_begin...x1_end, x2_end,...], where xi_begin the number of" "pixels added at the beginning of axis `i` and xi_end, the number of pixels added at the end of axis `i`." "This attribute cannot be used simultaneously with auto_pad attribute. If not present, the padding defaults" "to 0 along start and end of each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "group", "number of groups input channels and output channels are divided into. default is 1.", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto x_type = ctx.getInputType(0); auto w_type = ctx.getInputType(1); auto y_type = ctx.getOutputType(0); if (nullptr == x_type || nullptr == w_type || nullptr == y_type || x_type->value_case() != TypeProto::kTensorType || w_type->value_case() != TypeProto::kTensorType) { fail_type_inference( "inputs are expected to have tensor type and output type should not be null."); } // Right now we only support int32 y_type->mutable_tensor_type()->set_elem_type(TensorProto::INT32); convPoolShapeInference(ctx, true, false, 0, 1); })); void convTransposeShapeInference(InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); // we need at least two inputs to have a shape for this inference. if (!hasNInputShapes(ctx, 2)) { return; } int64_t group = getAttribute(ctx, "group", 1); auto input_shape = ctx.getInputType(0)->tensor_type().shape(); if (input_shape.dim_size() < 2) { return; // Input tensor should have at least two dimensions. } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); std::vector dilations; if (getRepeatedAttribute(ctx, "dilations", dilations)) { if (dilations.size() != n_input_dims) { return; } } else { dilations.assign(n_input_dims, 1); } std::vector strides; if (getRepeatedAttribute(ctx, "strides", strides)) { if (strides.size() != n_input_dims) { return; } } else { strides.assign(n_input_dims, 1); } std::vector kernel_shape; if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) { if (kernel_shape.size() != n_input_dims) { return; } } else { auto second_input_shape = ctx.getInputType(1)->tensor_type().shape(); for (int i = 2; i < second_input_shape.dim_size(); ++i) { if (!second_input_shape.dim(i).has_dim_value()) { return; } kernel_shape.push_back(second_input_shape.dim(i).dim_value()); } } std::vector effective_kernel_shape = kernel_shape; for (int i = 0; i < static_cast(kernel_shape.size()); i++) { // accounting for dilation, how big is the kernel in this dimension effective_kernel_shape[i] = (effective_kernel_shape[i] - 1) * dilations[i] + 1; } std::vector pads; if (getRepeatedAttribute(ctx, "pads", pads)) { if (pads.size() != n_input_dims * 2) { fail_shape_inference("Attribute pads has incorrect size"); } } else { pads.assign(n_input_dims * 2, 0); const auto* auto_pad_attr = ctx.getAttribute("auto_pad"); if ((nullptr != auto_pad_attr) && (auto_pad_attr->s() != "VALID")) { int input_dims_size = static_cast(n_input_dims); for (int i = 0; i < input_dims_size; ++i) { int64_t residual = 0; int64_t stride = strides[i]; if (stride > 1) { if (!input_shape.dim(2 + i).has_dim_value()) { continue; } residual = input_shape.dim(2 + i).dim_value(); while (residual >= stride) { residual -= stride; } } int64_t total_pad = residual == 0 ? effective_kernel_shape[i] - stride : effective_kernel_shape[i] - residual; if (total_pad < 0) total_pad = 0; int64_t half_pad_small = total_pad >> 1; int64_t half_pad_big = total_pad - half_pad_small; if (auto_pad_attr->s() == "SAME_UPPER") { pads[i] = half_pad_small; pads[i + input_dims_size] = half_pad_big; } else if (auto_pad_attr->s() == "SAME_LOWER") { pads[i] = half_pad_big; pads[i + input_dims_size] = half_pad_small; } } } } std::vector output_shape; bool output_shape_presented = true; if (getRepeatedAttribute(ctx, "output_shape", output_shape)) { if (output_shape.size() != n_input_dims) { return; } } else { output_shape_presented = false; } std::vector output_padding; if (getRepeatedAttribute(ctx, "output_padding", output_padding)) { if (output_padding.size() != n_input_dims) { // Added only to one side. return; } } else { output_padding.assign(n_input_dims, 0); } auto final_output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); *final_output_shape->add_dim() = input_shape.dim(0); *final_output_shape->add_dim() = ctx.getInputType(1)->tensor_type().shape().dim(1) * group; // channels should be the second dim of second input multiply // group. int size_of_output; if (output_shape_presented) { size_of_output = static_cast(output_shape.size()); for (int i = 0; i < size_of_output; ++i) { if (input_shape.dim(i + 2).has_dim_value()) { if (output_shape[i] < input_shape.dim(i + 2).dim_value()) { // TODO: throw exception? return; // output shape value cannot be smaller than the input shape // value } } final_output_shape->add_dim()->set_dim_value(output_shape[i]); } return; } else { size_of_output = input_shape.dim_size() - 2; for (int i = 0; i < size_of_output; ++i) { if (input_shape.dim(i + 2).has_dim_value()) { int64_t output_shape_dim = strides[i] * (input_shape.dim(i + 2).dim_value() - 1) + output_padding[i] + effective_kernel_shape[i] - pads[i] - pads[i + n_input_dims]; final_output_shape->add_dim()->set_dim_value(output_shape_dim); } else { final_output_shape->add_dim(); } } return; } } std::function ConvTransposeOpSchemaGenerator( const char* filter_desc) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( The convolution transpose operator consumes an input tensor and {filter_desc}, and computes the output. If the pads parameter is provided the shape of the output is calculated via the following equation: output_shape[i] = stride[i] * (input_size[i] - 1) + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - pads[start_i] - pads[end_i] output_shape can also be explicitly specified in which case pads values are auto generated using these equations: total_padding[i] = stride[i] * (input_size[i] - 1) + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i] If (auto_pads != SAME_UPPER): pads[start_i] = total_padding[i]/2; pads[end_i] = total_padding[i] - (total_padding[i]/2) Else: pads[start_i] = total_padding[i] - (total_padding[i]/2); pads[end_i] = (total_padding[i]/2). )DOC"; ReplaceAll(doc, "{filter_desc}", filter_desc);); schema.SetDoc(doc); schema.Input( 0, "X", "Input data tensor from previous layer; has size (N x C x H x W)" ", where N is the batch size, C is the number of channels, and" " H and W are the height and width. Note that this is for the 2D image. " "Otherwise the size is (N x C x D1 x D2 ... x Dn)", "T"); schema.Input( 1, "W", "The weight tensor that will be used in the " "convolutions; has size (C x M/group x kH x kW), where C " "is the number of channels, and kH and kW are the " "height and width of the kernel, and M is the number " "of feature maps. For more than 2 dimensions, the " "weight shape will be (C x M/group x k1 x k2 x ... x kn), " "where (k1 x k2 x ... x kn) is the dimension of the kernel. " "The number of channels in the output should be equal to W.shape[1] * group " "(assuming zero based indices of the shape array)", "T"); schema.Input( 2, "B", "Optional 1D bias to be added to the convolution, has size of M.", "T", OpSchema::Optional); schema.Output( 0, "Y", "Output data tensor that contains the result of the convolution. The " "output dimensions are functions of the kernel size, stride size, " "pad lengths and group count. " "The number of channels in the output should be equal to W.shape[1] * group " "(assuming zero based indices of the shape array)", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.Attr( "kernel_shape", "The shape of the convolution kernel. If not present, should be inferred from input W.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "output_shape", "The shape of the output can be explicitly set which will cause pads values to be auto generated. If output_shape is specified " "pads values are ignored. See doc for details for equations to generate pads", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "output_padding", "Additional elements added to the side with higher coordinate indices in the output. " "Each padding value in \"output_padding\" must be less than the corresponding stride/dilation dimension. " "By default, this attribute is a zero vector. " "Note that this attribute doesn't directly affect the computed output values. " "It only controls the selection of the computed values, " "so changing this attribute only adds or removes output elements. " "If \"output_shape\" is explicitly provided, " "\"output_padding\" does not contribute additional size to \"output_shape\" but " "participates in the computation of the needed padding amount. " "This is also called adjs or adjustment in some frameworks.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "dilations", "dilation value along each spatial axis of the filter. If not present, the dilation defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "strides", "Stride along each spatial axis. If not present, the stride defaults to 1 along each spatial axis.", AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "auto_pad", auto_pad_doc, AttributeProto::STRING, std::string("NOTSET")); schema.Attr("pads", pads_doc, AttributeProto::INTS, OPTIONAL_VALUE); schema.Attr( "group", "number of groups input channels and output channels are divided into.", AttributeProto::INT, static_cast(1)); schema.TypeAndShapeInferenceFunction( [](InferenceContext& ctx) { convTransposeShapeInference(ctx); }); }; } ONNX_OPERATOR_SET_SCHEMA( ConvTranspose, 11, OpSchema().FillUsing(ConvTransposeOpSchemaGenerator("a filter"))); // For GlobalPool operations. void globalPoolTypeShapeInference(InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); // needs at least one input with shape. if (!hasNInputShapes(ctx, 1)) { return; } auto input_shape = ctx.getInputType(0)->tensor_type().shape(); if (input_shape.dim_size() < 2) { return; } // first dim is the batch axis and the next is the number of channels. size_t n_input_dims = static_cast(input_shape.dim_size() - 2); // (N, C, 1, 1, ..., 1) auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape(); *output_shape->add_dim() = input_shape.dim(0); *output_shape->add_dim() = input_shape.dim(1); for (size_t i = 0; i < n_input_dims; ++i) { output_shape->add_dim()->set_dim_value(1); } } std::function GlobalPoolingOpSchemaGenerator( const char* op_type, const char* op) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Global{op_type} consumes an input tensor X and applies {op} pooling across the values in the same channel. This is equivalent to {op_type} with kernel size equal to the spatial dimension of input tensor.)DOC"; ReplaceAll(doc, "{op_type}", op_type); ReplaceAll(doc, "{op}", op);); schema.SetDoc(doc); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the width " "of the data. For non image case, the dimensions are " "in the form of (N x C x D1 x D2 ... Dn), " "where N is the batch size.", "T"); schema.Output( 0, "Y", "Output data tensor from pooling across the input " "tensor. The output tensor has the same rank as the input. " "The first two dimensions of output shape are the same as " "the input (N x C), while the other dimensions are all 1.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction( [](InferenceContext& ctx) { globalPoolTypeShapeInference(ctx); }); }; } ONNX_OPERATOR_SET_SCHEMA( GlobalAveragePool, 1, OpSchema().FillUsing( GlobalPoolingOpSchemaGenerator("AveragePool", "average"))); ONNX_OPERATOR_SET_SCHEMA( GlobalMaxPool, 1, OpSchema().FillUsing(GlobalPoolingOpSchemaGenerator("MaxPool", "max"))); std::function GlobalLpPoolingOpSchemaGenerator( const char* op_type, const char* op) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Global{op_type} consumes an input tensor X and applies {op} pooling across the values in the same channel. This is equivalent to {op_type} with kernel size equal to the spatial dimension of input tensor.)DOC"; ReplaceAll(doc, "{op_type}", op_type); ReplaceAll(doc, "{op}", op);); schema.SetDoc(doc); schema.Attr( "p", "p value of the Lp norm used to pool over the input data.", AttributeProto::INT, static_cast(2)); schema.Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the width " "of the data. For non image case, the dimensions are " "in the form of (N x C x D1 x D2 ... Dn), " "where N is the batch size.", "T"); schema.Output( 0, "Y", "Output data tensor from pooling across the input " "tensor. The output tensor has the same rank as the input. " "The first two dimensions of output shape are the same as " "the input (N x C), while the other dimensions are all 1.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeAndShapeInferenceFunction( [](InferenceContext& ctx) { globalPoolTypeShapeInference(ctx); }); }; } ONNX_OPERATOR_SET_SCHEMA( GlobalLpPool, 2, OpSchema().FillUsing( GlobalLpPoolingOpSchemaGenerator("LpPool", "lp pool"))); static const char* BatchNormalization_ver9_doc = R"DOC( Carries out batch normalization as described in the paper https://arxiv.org/abs/1502.03167. Depending on the mode it is being run, there are multiple cases for the number of outputs, which we list below: Output case #1: Y, mean, var, saved_mean, saved_var (training mode) Output case #2: Y (test mode) For previous (depreciated) non-spatial cases, implementors are suggested to flatten the input shape to (N x C*D1*D2 ..*Dn) before a BatchNormalization Op. )DOC"; ONNX_OPERATOR_SET_SCHEMA( BatchNormalization, 9, OpSchema() .NumOutputs({1, 5}) .SetDoc(BatchNormalization_ver9_doc + GenerateOptionalArgumentsDoc()) .Attr( "epsilon", "The epsilon value to use to avoid division by zero.", AttributeProto::FLOAT, 1e-5f) .Attr( "momentum", "Factor used in computing the running mean and variance." "e.g., running_mean = running_mean * momentum + mean * (1 - momentum).", AttributeProto::FLOAT, 0.9f) .Input( 0, "X", "Input data tensor from the previous operator; " "dimensions are in the form of (N x C x D1 x D2 ... Dn), " "where N is the batch size, C is the number of channels. " "Statistics are computed for every channel of C over N and D1 to Dn dimensions. " "For image data, input dimensions become (N x C x H x W). " "The op also accepts single dimension input of size N in which case C is assumed to be 1", "T") .Input(1, "scale", "Scale tensor of shape (C).", "T") .Input(2, "B", "Bias tensor of shape (C).", "T") .Input( 3, "mean", "running (training) or estimated (testing) mean tensor of shape (C).", "T") .Input( 4, "var", "running (training) or estimated (testing) variance tensor of shape (C).", "T") .Output(0, "Y", "The output tensor of the same shape as X", "T") .Output( 1, "mean", "The running mean after the BatchNormalization operator.", "T", OpSchema::Optional) .Output( 2, "var", "The running variance after the BatchNormalization operator.", "T", OpSchema::Optional) .Output( 3, "saved_mean", "Saved mean used during training to speed up gradient " "computation.", "T", OpSchema::Optional) .Output( 4, "saved_var", "Saved variance used during training to speed up " "gradient computation.", "T", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateShapeAndTypeFromFirstInput(ctx); // TODO in training mode, it may be possible to infer some of // the other outputs as well. })); static const char* InstanceNormalization_ver6_doc = R"DOC( Carries out instance normalization as described in the paper https://arxiv.org/abs/1607.08022. y = scale * (x - mean) / sqrt(variance + epsilon) + B, where mean and variance are computed per instance per channel. )DOC"; ONNX_OPERATOR_SET_SCHEMA( InstanceNormalization, 6, OpSchema() .SetDoc(InstanceNormalization_ver6_doc) .Attr( "epsilon", "The epsilon value to use to avoid division by zero.", AttributeProto::FLOAT, 1e-5f) .Input( 0, "input", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size.", "T") .Input( 1, "scale", "The input 1-dimensional scale tensor of size C.", "T") .Input(2, "B", "The input 1-dimensional bias tensor of size C.", "T") .Output( 0, "output", "The output tensor of the same shape as input.", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateShapeAndTypeFromFirstInput(ctx); })); static const char* LpNormalization_ver1_doc = R"DOC( Given a matrix, apply Lp-normalization along the provided axis. )DOC"; ONNX_OPERATOR_SET_SCHEMA( LpNormalization, 1, OpSchema() .Input(0, "input", "Input matrix", "T") .Output(0, "output", "Matrix after normalization", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .SetDoc(LpNormalization_ver1_doc) .Attr( "axis", "The axis on which to apply normalization, -1 mean last axis.", AttributeProto::INT, static_cast(-1)) .Attr( "p", "The order of the normalization, only 1 or 2 are supported.", AttributeProto::INT, static_cast(2)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateShapeAndTypeFromFirstInput(ctx); })); static const char* Dropout_ver12_doc = R"DOC( Dropout takes an input floating-point tensor, an optional input ratio (floating-point scalar) and an optional input training_mode (boolean scalar). It produces two tensor outputs, output (floating-point tensor) and mask (optional `Tensor`). If `training_mode` is true then the output Y will be a random dropout; Note that this Dropout scales the masked input data by the following equation, so to convert the trained model into inference mode, the user can simply not pass `training_mode` input or set it to false. ``` output = scale * data * mask, ``` where ``` scale = 1. / (1. - ratio). ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( Dropout, 12, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(Dropout_ver12_doc) + GenerateOptionalArgumentsDoc())) .Attr( "seed", "(Optional) Seed to the random generator, if not specified we will auto generate one.", AttributeProto::INT, OPTIONAL_VALUE) .Input(0, "data", "The input data as Tensor.", "T") .Input( 1, "ratio", "The ratio of random dropout, with value in [0, 1). If this input was not set, " "or if it was set to 0, the output would be a simple copy of the input. " "If it's non-zero, output will be a random dropout of the scaled input, which is typically " "the case during training. It is an optional value, if not specified it will default to 0.5.", "T1", OpSchema::Optional) .Input( 2, "training_mode", "If set to true then it indicates dropout is being used for training. It is an optional value hence unless " "specified explicitly, it is false. If it is false, ratio is ignored and the operation mimics inference mode where " "nothing will be dropped from the input data and if mask is requested as output it will contain all ones.", "T2", OpSchema::Optional) .Output(0, "output", "The output.", "T") .Output(1, "mask", "The output mask.", "T2", OpSchema::Optional) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors.") .TypeConstraint( "T1", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input 'ratio' types to float tensors.") .TypeConstraint( "T2", {"tensor(bool)"}, "Constrain output 'mask' types to boolean tensors.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (hasInputShape(ctx, 0)) { propagateShapeFromInputToOutput(ctx, 0, 0); } if (ctx.getNumInputs() > 1 && hasInputShape(ctx, 1)) { auto& ratio_input_shape = getInputShape(ctx, 1); if (static_cast(ratio_input_shape.dim_size()) != 0) { fail_shape_inference("Ratio of Dropout must be a scalar."); } } if (ctx.getNumInputs() > 2 && hasInputShape(ctx, 2)) { auto& training_mode_input_shape = getInputShape(ctx, 2); if (static_cast(training_mode_input_shape.dim_size()) != 0) { fail_shape_inference("training_mode of Dropout must be a scalar."); } } if (ctx.getNumOutputs() == 2) { updateOutputElemType(ctx, 1, TensorProto::BOOL); if (hasNInputShapes(ctx, 1)) { propagateShapeFromInputToOutput(ctx, 0, 1); } } })); static const char* Shrink_ver9_doc = R"DOC( Shrink takes one input data (Tensor) and produces one Tensor output, having same datatype and shape with input. It has two attributes, lambd and bias. The formula of this operator is: If x < -lambd, y = x + bias; If x > lambd, y = x - bias; Otherwise, y = 0. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Shrink, 9, OpSchema() .SetDoc(Shrink_ver9_doc) .Attr( "lambd", "The lambd value for the Shrink formulation. Default is 0.5.", AttributeProto::FLOAT, 0.5f) .Attr( "bias", "The bias value added to output. Default is 0.", AttributeProto::FLOAT, 0.0f) .Input(0, "input", "The input data as Tensor.", "T") .Output(0, "output", "The output.", "T") .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrains input to only numeric types.") .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* Flatten_ver11_doc = R"DOC( Flattens the input tensor into a 2D matrix. If input tensor has shape (d_0, d_1, ... d_n) then the output will have shape (d_0 X d_1 ... d_(axis-1), d_axis X d_(axis+1) ... X dn). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Flatten, 11, OpSchema() .SetDoc(Flatten_ver11_doc) .Input(0, "input", "A tensor of rank >= axis.", "T") .Output( 0, "output", "A 2D tensor with the contents of the input tensor, " "with input dimensions up to axis flattened to the outer dimension " "of the output and remaining input dimensions flattened into the inner " "dimension of the output.", "T") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input and output to all tensor types.") .Attr( "axis", "Indicate up to which input dimensions " "(exclusive) should be flattened to the outer dimension of the output. " "The value for axis must be in the range [-r, r], where r is the rank of the input tensor. " "Negative value means counting dimensions from the back. " "When axis = 0, the shape of the output tensor is (1, (d_0 X d_1 ... d_n), " "where the shape of the input tensor is (d_0, d_1, ... d_n). ", AttributeProto::INT, static_cast(1)) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 0, 0); if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); int rank = static_cast(input_shape.dim_size()); int axis = static_cast(getAttribute(ctx, "axis", 1)); if (axis < 0) { axis += rank; } if (axis > rank || axis < 0) { fail_shape_inference( "Invalid value(", axis, ") for attribute 'axis'"); } // TODO: is the operation defined for input-rank < 2? updateOutputShape( ctx, 0, {multiplyDims(input_shape, 0, axis), multiplyDims(input_shape, axis, rank)}); })); static const char* LRN_ver1_doc = R"DOC( Local Response Normalization proposed in the [AlexNet paper](https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf). It normalizes over local input regions. The local region is defined across the channels. For an element X[n, c, d1, ..., dk] in a tensor of shape (N x C x D1 x D2, ..., Dk), its region is {X[n, i, d1, ..., dk] | max(0, c - floor((size - 1) / 2)) <= i <= min(C - 1, c + ceil((size - 1) / 2))}. square_sum[n, c, d1, ..., dk] = sum(X[n, i, d1, ..., dk] ^ 2), where max(0, c - floor((size - 1) / 2)) <= i <= min(C - 1, c + ceil((size - 1) / 2)). Y[n, c, d1, ..., dk] = X[n, c, d1, ..., dk] / (bias + alpha / size * square_sum[n, c, d1, ..., dk] ) ^ beta )DOC"; ONNX_OPERATOR_SET_SCHEMA( LRN, 1, OpSchema() .Attr("size", "The number of channels to sum over", AttributeProto::INT) .Attr("alpha", "Scaling parameter.", AttributeProto::FLOAT, 0.0001f) .Attr("beta", "The exponent.", AttributeProto::FLOAT, 0.75f) .Attr("bias", "", AttributeProto::FLOAT, 1.0f) .Input( 0, "X", "Input data tensor from the previous operator; " "dimensions for image case are (N x C x H x W), " "where N is the batch size, C is the number of " "channels, and H and W are the height and the " "width of the data. For non image case, the " "dimensions are in the form of " "(N x C x D1 x D2 ... Dn), where N is the batch " "size. Optionally, if dimension denotation is " "in effect, the operation expects the input " "data tensor to arrive with the dimension denotation " "of [DATA_BATCH, DATA_CHANNEL, DATA_FEATURE, DATA_FEATURE ...].", "T") .Output( 0, "Y", "Output tensor, which has the shape and type as input tensor", "T") .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output " " types to float tensors.") .SetDoc(LRN_ver1_doc) .TypeAndShapeInferenceFunction(propagateShapeAndTypeFromFirstInput)); static const char* TfIdfVectorizer_ver9_doc = R"DOC( This transform extracts n-grams from the input sequence and save them as a vector. Input can be either a 1-D or 2-D tensor. For 1-D input, output is the n-gram representation of that input. For 2-D input, the output is also a 2-D tensor whose i-th row is the n-gram representation of the i-th input row. More specifically, if input shape is [C], the corresponding output shape would be [max(ngram_indexes) + 1]. If input shape is [N, C], this operator produces a [N, max(ngram_indexes) + 1]-tensor. In contrast to standard n-gram extraction, here, the indexes of extracting an n-gram from the original sequence are not necessarily consecutive numbers. The discontinuity between indexes are controlled by the number of skips. If the number of skips is 2, we should skip two tokens when scanning through the original sequence. Let's consider an example. Assume that input sequence is [94, 17, 36, 12, 28] and the number of skips is 2. The associated 2-grams are [94, 12] and [17, 28] respectively indexed by [0, 3] and [1, 4]. If the number of skips becomes 0, the 2-grams generated are [94, 17], [17, 36], [36, 12], [12, 28] indexed by [0, 1], [1, 2], [2, 3], [3, 4], respectively. The output vector (denoted by Y) stores the count of each n-gram; Y[ngram_indexes[i]] indicates the times that the i-th n-gram is found. The attribute ngram_indexes is used to determine the mapping between index i and the corresponding n-gram's output coordinate. If pool_int64s is [94, 17, 17, 36], ngram_indexes is [1, 0], ngram_counts=[0, 0], then the Y[0] (first element in Y) and Y[1] (second element in Y) are the counts of [17, 36] and [94, 17], respectively. An n-gram which cannot be found in pool_strings/pool_int64s should be ignored and has no effect on the output. Note that we may consider all skips up to S when generating the n-grams. The examples used above are true if mode is "TF". If mode is "IDF", all the counts larger than 1 would be truncated to 1 and the i-th element in weights would be used to scale (by multiplication) the count of the i-th n-gram in pool. If mode is "TFIDF", this operator first computes the counts of all n-grams and then scale them by the associated values in the weights attribute. Only one of pool_strings and pool_int64s can be set. If pool_int64s is set, the input should be an integer tensor. If pool_strings is set, the input must be a string tensor. )DOC"; ONNX_OPERATOR_SET_SCHEMA( TfIdfVectorizer, 9, OpSchema() .Input(0, "X", "Input for n-gram extraction", "T") .Output(0, "Y", "Ngram results", "T1") .TypeConstraint( "T", {"tensor(string)", "tensor(int32)", "tensor(int64)"}, "Input is ether string UTF-8 or int32/int64") .TypeConstraint("T1", {"tensor(float)"}, "1-D tensor of floats") .Attr( "max_gram_length", "Maximum n-gram length. If this value is 3, 3-grams will be used to generate the output.", AttributeProto::INT) .Attr( "min_gram_length", "Minimum n-gram length. If this value is 2 and max_gram_length is 3, output may contain counts of 2-grams and 3-grams.", AttributeProto::INT) .Attr( "max_skip_count", "Maximum number of items (integers/strings) to be skipped when constructing an n-gram from X. " "If max_skip_count=1, min_gram_length=2, max_gram_length=3, this operator may generate 2-grams " "with skip_count=0 and skip_count=1, and 3-grams with skip_count=0 and skip_count=1", AttributeProto::INT) .Attr( "pool_strings", "List of strings n-grams learned from the training set. Either this or pool_int64s attributes must be present but not both. " "It's an 1-D tensor starting with the collections of all 1-grams and ending with the collections of n-grams. " "The i-th element in pool stores the n-gram that should be mapped to coordinate ngram_indexes[i] in the output vector.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "pool_int64s", "List of int64 n-grams learned from the training set. Either this or pool_strings attributes must be present but not both. " "It's an 1-D tensor starting with the collections of all 1-grams and ending with the collections of n-grams. " "The i-th element in pool stores the n-gram that should be mapped to coordinate ngram_indexes[i] in the output vector.", AttributeProto::INTS, OPTIONAL_VALUE) .Attr( "ngram_counts", "The starting indexes of 1-grams, 2-grams, and so on in pool. " "It is useful when determining the boundary between two consecutive collections of n-grams. " "For example, if ngram_counts is [0, 17, 36], the first index (zero-based) of 1-gram/2-gram/3-gram " "in pool are 0/17/36. This format is essentially identical to CSR (or CSC) sparse matrix format, " "and we choose to use this due to its popularity.", AttributeProto::INTS) .Attr( "ngram_indexes", "list of int64s (type: AttributeProto::INTS). This list is parallel to the specified 'pool_*' attribute. " "The i-th element in ngram_indexes indicate the coordinate of the i-th n-gram in the output tensor.", AttributeProto::INTS) .Attr( "weights", "list of floats. This attribute stores the weight of each n-gram in pool. The i-th element in weights " "is the weight of the i-th n-gram in pool. Its length equals to the size of ngram_indexes. " "By default, weights is an all-one tensor.This attribute is used when mode is \"IDF\" or \"TFIDF\" " "to scale the associated word counts.", AttributeProto::FLOATS, OPTIONAL_VALUE) .Attr( "mode", "The weighting criteria. It can be one of \"TF\" (term frequency), " "\"IDF\" (inverse document frequency), and \"TFIDF\" (the combination of TF and IDF)", AttributeProto::STRING) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); output_elem_type->set_elem_type(TensorProto::FLOAT); if (hasInputShape(ctx, 0)) { std::vector ngram_indexes; getRepeatedAttribute(ctx, "ngram_indexes", ngram_indexes); if (ngram_indexes.empty() || !std::all_of( ngram_indexes.cbegin(), ngram_indexes.cend(), [](int64_t i) { return i >= 0; })) { fail_shape_inference( "ngram_indexes must be non-empty with no negative values"); } auto greatest_hit = std::max_element(ngram_indexes.cbegin(), ngram_indexes.cend()); auto max_last_axis = *greatest_hit + 1; TensorShapeProto output_shape; auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); auto dim_size = input_shape.dim_size(); if (dim_size == 1) { output_shape.add_dim()->set_dim_value(max_last_axis); } else if (dim_size == 2) { *output_shape.add_dim() = input_shape.dim(0); output_shape.add_dim()->set_dim_value(max_last_axis); } else { fail_shape_inference("Input tensor must have rank 1 or 2"); } updateOutputShape(ctx, 0, output_shape); } }) .SetDoc(TfIdfVectorizer_ver9_doc)); static const char* StringNormalizer_ver10_doc = R"DOC( StringNormalization performs string operations for basic cleaning. This operator has only one input (denoted by X) and only one output (denoted by Y). This operator first examines the elements in the X, and removes elements specified in "stopwords" attribute. After removing stop words, the intermediate result can be further lowercased, uppercased, or just returned depending the "case_change_action" attribute. This operator only accepts [C]- and [1, C]-tensor. If all elements in X are dropped, the output will be the empty value of string tensor with shape [1] if input shape is [C] and shape [1, 1] if input shape is [1, C]. )DOC"; ONNX_OPERATOR_SET_SCHEMA( StringNormalizer, 10, OpSchema() .Input(0, "X", "UTF-8 strings to normalize", "tensor(string)") .Output(0, "Y", "UTF-8 Normalized strings", "tensor(string)") .Attr( std::string("case_change_action"), std::string( "string enum that cases output to be lowercased/uppercases/unchanged." " Valid values are \"LOWER\", \"UPPER\", \"NONE\". Default is \"NONE\""), AttributeProto::STRING, std::string("NONE")) .Attr( std::string("is_case_sensitive"), std::string( "Boolean. Whether the identification of stop words in X is case-sensitive. Default is false"), AttributeProto::INT, static_cast(0)) .Attr( "stopwords", "List of stop words. If not set, no word would be removed from X.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "locale", "Environment dependent string that denotes the locale according to which output strings needs to be upper/lowercased." "Default en_US or platform specific equivalent as decided by the implementation.", AttributeProto::STRING, OPTIONAL_VALUE) .SetDoc(StringNormalizer_ver10_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto output_elem_type = ctx.getOutputType(0)->mutable_tensor_type(); output_elem_type->set_elem_type(TensorProto::STRING); if (!hasInputShape(ctx, 0)) { return; } TensorShapeProto output_shape; auto& input_shape = ctx.getInputType(0)->tensor_type().shape(); auto dim_size = input_shape.dim_size(); // Last axis dimension is unknown if we have stop-words since we do // not know how many stop-words are dropped if (dim_size == 1) { // Unknown output dimension output_shape.add_dim(); } else if (dim_size == 2) { // Copy B-dim auto& b_dim = input_shape.dim(0); if (!b_dim.has_dim_value() || b_dim.dim_value() != 1) { fail_shape_inference( "Input shape must have either [C] or [1,C] dimensions where C > 0"); } *output_shape.add_dim() = b_dim; output_shape.add_dim(); } else { fail_shape_inference( "Input shape must have either [C] or [1,C] dimensions where C > 0"); } updateOutputShape(ctx, 0, output_shape); })); static const char* mvn_ver9_doc = R"DOC( A MeanVarianceNormalization Function: Perform mean variance normalization on the input tensor X using formula:
``` (X-EX)/sqrt(E(X-EX)^2) ``` )DOC"; static std::vector mvn_default_axes = {0, 2, 3}; ONNX_OPERATOR_SET_SCHEMA( MeanVarianceNormalization, 9, OpSchema() .SetDoc(mvn_ver9_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .Attr( "axes", "A list of integers, along which to reduce. The default is to " "caculate along axes [0,2,3] for calculating mean and variance " "along each channel. Two variables with the same C-coordinate " "are associated with the same mean and variance.", AttributeProto::INTS, mvn_default_axes) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to all numeric tensors.") .FunctionBody(FunctionBodyHelper::BuildNodes( {// nodes: {outputs, op, inputs, attributes} FunctionBodyHelper::Const("Exponent", 2.0f), FunctionBodyHelper::Const("Epsilon", float(1e-9)), {{"X_RM"}, "ReduceMean", {"X"}, {MakeRefAttribute("axes", AttributeProto::INTS)}}, {{"EX_squared"}, "Pow", {"X_RM", "Exponent"}}, {{"X_squared"}, "Pow", {"X", "Exponent"}}, {{"E_Xsquared"}, "ReduceMean", {"X_squared"}, {MakeRefAttribute("axes", AttributeProto::INTS)}}, {{"Variance"}, "Sub", {"E_Xsquared", "EX_squared"}}, {{"STD"}, "Sqrt", {"Variance"}}, {{"X_variance"}, "Sub", {"X", "X_RM"}}, {{"Processed_STD"}, "Add", {"STD", "Epsilon"}}, {{"Y"}, "Div", {"X_variance", "Processed_STD"}}}))); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/quantization/0000775000000000000000000000000013655345213015641 5ustar rootrootonnx-1.7.0/onnx/defs/quantization/defs.cc0000664000000000000000000001616013655345213017075 0ustar rootroot// Copyright (c) Facebook Inc. and Microsoft Corporation. // Licensed under the MIT license. #include "onnx/defs/function.h" #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { static const char* QuantizeLinear_ver10_doc = R"DOC( The linear per-tensor/layer quantization operator. It consumes a high precision tensor, a scale, a zero point to compute the low precision / quantized tensor. The quantization formula is y = saturate ((x / y_scale) + y_zero_point). For saturation, it saturates to [0, 255] if it's uint8, or [-128, 127] if it's int8. For (x / y_scale), it's rounding to nearest ties to even. Refer to https://en.wikipedia.org/wiki/Rounding for details. 'y_zero_point' and 'y' must have same type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( QuantizeLinear, 10, OpSchema() .Input(0, "x", "N-D full precision Input tensor to be quantized.", "T1") .Input( 1, "y_scale", "Scale for doing quantization to get 'y'. It's a scalar, which means a per-tensor/layer quantization.", "tensor(float)") .Input( 2, "y_zero_point", "Zero point for doing quantization to get 'y'. It's a scalar, which means a per-tensor/layer quantization. " "Default value is uint8 typed 0 if it's not specified.", "T2", OpSchema::Optional) .Output( 0, "y", "N-D quantized output tensor. It has same shape as input 'x'.", "T2") .TypeConstraint( "T1", {"tensor(float)", "tensor(int32)"}, "Constrain 'x' to float or int32 tensor.") .TypeConstraint( "T2", {"tensor(int8)", "tensor(uint8)"}, "Constrain 'y_zero_point' and 'y' to 8-bit integer tensor.") .SetDoc(QuantizeLinear_ver10_doc) .TypeAndShapeInferenceFunction( [](ONNX_NAMESPACE::InferenceContext& ctx) { propagateElemTypeFromInputToOutput(ctx, 2, 0); if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); updateOutputShape(ctx, 0, input_shape); })); static const char* DequantizeLinear_ver10_doc = R"DOC( The linear dequantization operator. It consumes a quantized tensor, a scale, a zero point to compute the full precision tensor. The dequantization formula is y = (x - x_zero_point) * x_scale. 'x_scale' and 'x_zero_point' must have same shape. 'x_zero_point' and 'x' must have same type. 'x' and 'y' must have same shape. In the case of dequantizing int32, there's no zero point (zero point is supposed to be 0). )DOC"; ONNX_OPERATOR_SET_SCHEMA( DequantizeLinear, 10, OpSchema() .Input(0, "x", "N-D quantized input tensor to be de-quantized.", "T") .Input( 1, "x_scale", "Scale for input 'x'. It's a scalar, which means a per-tensor/layer quantization.", "tensor(float)") .Input( 2, "x_zero_point", "Zero point for input 'x'. It's a scalar, which means a per-tensor/layer quantization. " "It's optional. 0 is the default value when it's not specified.", "T", OpSchema::Optional) .Output( 0, "y", "N-D full precision output tensor. It has same shape as input 'x'.", "tensor(float)") .TypeConstraint( "T", {"tensor(int8)", "tensor(uint8)", "tensor(int32)"}, "Constrain 'x_zero_point' and 'x' to 8-bit/32-bit integer tensor.") .SetDoc(DequantizeLinear_ver10_doc) .TypeAndShapeInferenceFunction( [](ONNX_NAMESPACE::InferenceContext& ctx) { auto y_type = ctx.getOutputType(0); // only float is supported y_type->mutable_tensor_type()->set_elem_type( ONNX_NAMESPACE::TensorProto::FLOAT); if (!hasInputShape(ctx, 0)) return; auto& input_shape = getInputShape(ctx, 0); updateOutputShape(ctx, 0, input_shape); })); static const char* DynamicQuantizeLinear_ver11_doc = R"DOC( A Function to fuse calculation for Scale, Zero Point and FP32->8Bit convertion of FP32 Input data. Outputs Scale, ZeroPoint and Quantized Input for a given FP32 Input. Scale is calculated as: ``` y_scale = (max(x) - min(x))/(qmax - qmin) * where qmax and qmin are max and min values for quantization range .i.e [0, 255] in case of uint8 * data range is adjusted to include 0. ``` Zero point is calculated as: ``` intermediate_zero_point = qmin - min(x)/y_scale y_zero_point = cast(round(saturate(itermediate_zero_point))) * where qmax and qmin are max and min values for quantization range .i.e [0, 255] in case of uint8 * for saturation, it saturates to [0, 255] if it's uint8, or [-127, 127] if it's int8. Right now only uint8 is supported. * rounding to nearest ties to even. ``` Data quantization formula is: ``` y = saturate (round (x / y_scale) + y_zero_point) * for saturation, it saturates to [0, 255] if it's uint8, or [-127, 127] if it's int8. Right now only uint8 is supported. * rounding to nearest ties to even. ``` )DOC"; ONNX_OPERATOR_SET_SCHEMA( DynamicQuantizeLinear, 11, OpSchema() .SetDoc(DynamicQuantizeLinear_ver11_doc) .Input(0, "x", "Input tensor", "T1") .Output(0, "y", "Quantized output tensor", "T2") .Output(1, "y_scale", "Output scale. It's a scalar, which means a per-tensor/layer quantization.", "tensor(float)") .Output(2, "y_zero_point", "Output zero point. It's a scalar, which means a per-tensor/layer quantization.", "T2") .TypeConstraint( "T1", {"tensor(float)"}, "Constrain 'x' to float tensor.") .TypeConstraint( "T2", {"tensor(uint8)"}, "Constrain 'y_zero_point' and 'y' to 8-bit unsigned integer tensor.") .FunctionBody(FunctionBodyHelper::BuildNodes( {// nodes: {outputs, op, inputs, attributes} FunctionBodyHelper::Const("Q_Min", 0.f), FunctionBodyHelper::Const("Q_Max", 255.f), {{"X_Min"}, "ReduceMin", {"x"}, {MakeAttribute("keepdims", int64_t(0))}}, {{"X_Min_Adjusted"}, "Min", {"X_Min", "Q_Min"}}, {{"X_Max"}, "ReduceMax", {"x"}, {MakeAttribute("keepdims", int64_t(0))}}, {{"X_Max_Adjusted"}, "Max", {"X_Max", "Q_Min"}}, {{"X_Range"}, "Sub", {"X_Max_Adjusted", "X_Min_Adjusted"}}, {{"Scale"}, "Div", {"X_Range", "Q_Max"}}, {{"Min_Scaled"}, "Div", {"X_Min_Adjusted", "Scale"}}, {{"Initial_ZeroPoint_FP"}, "Sub", {"Q_Min", "Min_Scaled"}}, {{"Clipped_ZeroPoint_FP"}, "Clip", {"Initial_ZeroPoint_FP", "Q_Min", "Q_Max"}}, {{"Rounded_ZeroPoint_FP"}, "Round", {"Clipped_ZeroPoint_FP"}}, {{"Zeropoint"}, "Cast", {"Rounded_ZeroPoint_FP"}, {MakeAttribute("to", int64_t(2))}}, {{"y_scale"}, "Identity", {"Scale"}}, {{"y_zero_point"}, "Identity", {"Zeropoint"}}, {{"y"}, "QuantizeLinear", {"x", "Scale", "Zeropoint"}}}))); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/operator_sets_preview.h0000664000000000000000000000260713655345213017723 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { // Declare training operators. class ONNX_PREVIEW_OPERATOR_SET_SCHEMA_CLASS_NAME(1, Gradient); class ONNX_PREVIEW_OPERATOR_SET_SCHEMA_CLASS_NAME(1, GraphCall); class ONNX_PREVIEW_OPERATOR_SET_SCHEMA_CLASS_NAME(1, Momentum); class ONNX_PREVIEW_OPERATOR_SET_SCHEMA_CLASS_NAME(1, Adagrad); class ONNX_PREVIEW_OPERATOR_SET_SCHEMA_CLASS_NAME(1, Adam); // Iterate over schema from ai.onnx.training version 1 class OpSet_OnnxPreview_ver1 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Register preview operators. inline void RegisterOnnxPreviewOperatorSetSchema() { // Preview operators should have only one version. // If changes are needed for a specific preview operator, // its spec should be modified without increasing its version. RegisterOpSetSchema(); } } // namespace ONNX_NAMESPACEonnx-1.7.0/onnx/defs/tensor_util.cc0000664000000000000000000000576713655345213016010 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "tensor_util.h" #include #include "onnx/common/platform_helpers.h" namespace ONNX_NAMESPACE { #define DEFINE_PARSE_DATA(type, typed_data_fetch) \ template <> \ const std::vector ParseData(const Tensor* tensor) { \ std::vector res; \ if (!tensor->is_raw_data()) { \ const auto& data = tensor->typed_data_fetch(); \ res.insert(res.end(), data.begin(), data.end()); \ return res; \ } \ /* make copy as we may have to reverse bytes */ \ std::string raw_data = tensor->raw(); \ /* okay to remove const qualifier as we have already made a copy */ \ char* bytes = const_cast(raw_data.c_str()); \ /*onnx is little endian serialized always-tweak byte order if needed*/ \ if (!is_processor_little_endian()) { \ const size_t element_size = sizeof(type); \ const size_t num_elements = raw_data.size() / element_size; \ for (size_t i = 0; i < num_elements; ++i) { \ char* start_byte = bytes + i * element_size; \ char* end_byte = start_byte + element_size - 1; \ /* keep swapping */ \ for (size_t count = 0; count < element_size / 2; ++count) { \ char temp = *start_byte; \ *start_byte = *end_byte; \ *end_byte = temp; \ ++start_byte; \ --end_byte; \ } \ } \ } \ res.insert( \ res.end(), \ reinterpret_cast(bytes), \ reinterpret_cast(bytes + raw_data.size())); \ return res; \ } DEFINE_PARSE_DATA(int32_t, int32s) DEFINE_PARSE_DATA(int64_t, int64s) DEFINE_PARSE_DATA(float, floats) DEFINE_PARSE_DATA(double, doubles) } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/operator_sets.h0000664000000000000000000013056613655345213016170 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { // Forward declarations for ai.onnx version 1 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Abs); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Add); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, And); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ArgMax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ArgMin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, AveragePool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, BatchNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Cast); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Ceil); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Clip); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Concat); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Constant); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Conv); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ConvTranspose); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, DepthToSpace); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Div); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Dropout); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Elu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Equal); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Exp); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Flatten); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Floor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, GRU); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Gather); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Gemm); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, GlobalAveragePool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, GlobalLpPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, GlobalMaxPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Greater); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, HardSigmoid); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Hardmax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Identity); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, If); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, InstanceNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, LRN); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, LSTM); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, LeakyRelu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Less); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Log); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, LogSoftmax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Loop); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, LpNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, LpPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, MatMul); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Max); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, MaxPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, MaxRoiPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Mean); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Min); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Mul); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Neg); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Not); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Or); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, PRelu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Pad); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Pow); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, RNN); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, RandomNormal); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, RandomNormalLike); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, RandomUniform); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, RandomUniformLike); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Reciprocal); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceL1); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceL2); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceLogSum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceLogSumExp); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceMax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceMean); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceMin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceProd); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceSum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, ReduceSumSquare); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Relu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Reshape); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Selu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Shape); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Sigmoid); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Size); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Slice); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Softmax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Softplus); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Softsign); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, SpaceToDepth); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Split); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Sqrt); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Squeeze); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Sub); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Sum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Tanh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Tile); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, TopK); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Transpose); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Unsqueeze); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Upsample); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 1, Xor); // Iterate over schema from ai.onnx version 1 class OpSet_Onnx_ver1 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 2 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 2, GlobalLpPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 2, LpPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 2, Pad); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 2, Split); // Iterate over schema from ai.onnx version 2 class OpSet_Onnx_ver2 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 3 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 3, GRU); // Iterate over schema from ai.onnx version 3 class OpSet_Onnx_ver3 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 4 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 4, Concat); // Iterate over schema from ai.onnx version 4 class OpSet_Onnx_ver4 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 5 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 5, Reshape); // Iterate over schema from ai.onnx version 5 class OpSet_Onnx_ver5 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 6 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Abs); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Add); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, BatchNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Cast); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Ceil); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Clip); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Div); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Dropout); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Elu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Exp); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Floor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Gemm); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, HardSigmoid); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, InstanceNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, LeakyRelu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Log); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Max); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Mean); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Min); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Mul); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Neg); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, PRelu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Reciprocal); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Relu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Selu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Sigmoid); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Sqrt); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Sub); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Sum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Tanh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 6, Tile); // Iterate over schema from ai.onnx version 6 class OpSet_Onnx_ver6 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 7 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Acos); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Add); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, And); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Asin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Atan); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, AveragePool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, BatchNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Cos); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Div); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Dropout); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Equal); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Gemm); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Greater); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, GRU); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Less); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, LSTM); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Mul); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Or); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Pow); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, RNN); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Sin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Sub); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Tan); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Upsample); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Multinomial); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, Xor); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 7, PRelu); // Iterate over schema from ai.onnx version 7 class OpSet_Onnx_ver7 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 8 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, Expand); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, Max); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, Min); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, Sum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, Mean); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, MaxPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 8, Scan); // Iterate over schema from ai.onnx version 8 class OpSet_Onnx_ver8 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 9 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, BatchNormalization); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Compress); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, ConstantOfShape); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, EyeLike); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Greater); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Less); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Upsample); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, MaxUnpool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Constant); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, MatMul); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, OneHot); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, PRelu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Gemm); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Flatten); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Sinh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Cosh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Asinh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Acosh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Atanh); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Shrink); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, IsNaN); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Sign); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Scan); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Erf); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Scatter); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Where); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Cast); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, NonZero); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, TfIdfVectorizer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, MeanVarianceNormalization); // Iterate over schema from ai.onnx version 9 class OpSet_Onnx_ver9 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); // Add more types' support to Constant/MatMul/PRelu/Gemm/Flatten op. fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 10 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, StringNormalizer); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, Upsample); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, Resize); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, TopK); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, MaxPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, Mod); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, AveragePool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, Slice); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, ThresholdedRelu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, Dropout); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, MatMulInteger); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, QLinearMatMul); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, ConvInteger); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, QLinearConv); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, QuantizeLinear); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, DequantizeLinear); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, IsInf); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, NonMaxSuppression); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, ReverseSequence); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 10, RoiAlign); // Iterate over schema from ai.onnx version 10 class OpSet_Onnx_ver10 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 11 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Loop); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, CumSum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Round); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, BitShift); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Unique); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, TopK); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, DepthToSpace); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Equal); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Constant); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, DynamicQuantizeLinear); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, GatherElements); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ScatterElements); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Scatter); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Clip); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Resize); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Range); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Det); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ScatterND); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, GatherND); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Gather); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, OneHot); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Slice); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Squeeze); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Unsqueeze); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Flatten); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ArgMax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ArgMin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceL1); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceL2); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceLogSum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceLogSumExp); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceMax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceMean); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceMin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceProd); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceSum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ReduceSumSquare); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Compress); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Concat); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Hardmax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, LogSoftmax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Softmax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Scan); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Split); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, AveragePool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, MaxPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, MaxUnpool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, LpPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Conv); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ConvTranspose); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SequenceEmpty); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SequenceConstruct); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SequenceInsert); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SequenceAt); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SequenceErase); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SequenceLength); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, SplitToSequence); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, ConcatFromSequence); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Pad); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, Gemm); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, If); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, NonMaxSuppression); // Iterate over schema from ai.onnx version 11 class OpSet_Onnx_ver11 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; // Forward declarations for ai.onnx version 12 class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, ArgMax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, ArgMin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Clip); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Einsum); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, MaxPool); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, ReduceMax); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, ReduceMin); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, GatherND); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, NegativeLogLikelihoodLoss); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Dropout); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Constant); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Celu); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Max); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Min); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, LessOrEqual); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, GreaterOrEqual); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, SoftmaxCrossEntropyLoss); class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 12, Pow); // Iterate over schema from ai.onnx version 12 class OpSet_Onnx_ver12 { public: static void ForEachSchema(std::function fn) { fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); fn(GetOpSchema()); } }; inline void RegisterOnnxOperatorSetSchema() { RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); RegisterOpSetSchema(); } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/rnn/0000775000000000000000000000000013655345213013710 5ustar rootrootonnx-1.7.0/onnx/defs/rnn/old.cc0000664000000000000000000005526613655345213015013 0ustar rootroot#include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { std::function RNNDocGeneratorOld(const char* /*name*/) { return [=](OpSchema& schema) { schema.Attr( "direction", "Specify if the RNN is forward, reverse, or bidirectional. " "Must be one of forward (default), reverse, or bidirectional.", AttributeProto::STRING, std::string("foward")); schema.Attr( "hidden_size", "Number of neurons in the hidden layer", AttributeProto::INT, OPTIONAL_VALUE); schema.Attr( "activation_alpha", "Optional scaling values used by some activation functions. The values " "are consumed in the order of activation functions, for example (f, g, h) " "in LSTM.", AttributeProto::FLOATS, OPTIONAL_VALUE); schema.Attr( "activation_beta", "Optional scaling values used by some activation functions. The values " "are consumed in the order of activation functions, for example (f, g, h) " "in LSTM.", AttributeProto::FLOATS, OPTIONAL_VALUE); schema.Attr( "output_sequence", "The sequence output for the hidden is optional if 0. Default 0.", AttributeProto::INT, static_cast(0)); schema.Attr( "clip", "Cell clip threshold. Clipping bounds the elements of a tensor " "in the range of [-threshold, +threshold] and is applied to the input " "of activations. No clip if not specified.", AttributeProto::FLOAT, OPTIONAL_VALUE); schema.Input( 0, "X", "The input sequences packed (and potentially padded) into one 3-D " "tensor with the shape of `[seq_length, batch_size, input_size]`.", "T"); schema.Input( 4, "sequence_lens", "Optional tensor specifying lengths of the sequences in a batch. " "If not specified - assumed all sequences in the batch to have " "length `seq_length`. It has shape `[batch_size]`.", "T1", OpSchema::Optional); schema.Input( 5, "initial_h", "Optional initial value of the hidden. If not specified - assumed " "to be 0. It has shape `[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional); schema.Output( 0, "Y", "A tensor that concats all the intermediate output values of the hidden. " "It has shape `[seq_length, num_directions, batch_size, hidden_size]`. " "It is optional if `output_sequence` is 0.", "T", OpSchema::Optional); schema.Output( 1, "Y_h", "The last output value of the hidden. It has shape " "`[num_directions, batch_size, hidden_size]`.", "T"); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeConstraint( "T1", {"tensor(int32)"}, "Constrain seq_lens to integer tensor."); }; } static const char* GRU_ver1_doc = R"DOC( Computes an one-layer GRU. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `z` - update gate `r` - reset gate `h` - hidden gate `t` - time step (t-1 means previous time step) `W[zrh]` - W parameter weight matrix for update, reset, and hidden gates `R[zrh]` - R recurrence weight matrix for update, reset, and hidden gates `Wb[zrh]` - W bias vectors for update, reset, and hidden gates `Rb[zrh]` - R bias vectors for update, reset, and hidden gates `WB[zrh]` - W parameter weight matrix for backward update, reset, and hidden gates `RB[zrh]` - R recurrence weight matrix for backward update, reset, and hidden gates `WBb[zrh]` - W bias vectors for backward update, reset, and hidden gates `RBb[zrh]` - R bias vectors for backward update, reset, and hidden gates `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Sigmoid, g=Tanh): - zt = f(Xt*(Wz^T) + Ht-1*Rz + Wbz + Rbz) - rt = f(Xt*(Wr^T) + Ht-1*Rr + Wbr + Rbr) - ht = g(Xt*(Wh^T) + (rt (.) Ht-1)*Rh + Rbh + Wbh) # default, when linear_before_reset = 0 - ht = g(Xt*(Wh^T) + (rt (.) (Ht-1*Rh + Rbh) + Wbh) # when linear_before_reset != 0 - Ht = (1 - zt) (.) ht + zt (.) Ht-1 )DOC"; ONNX_OPERATOR_SET_SCHEMA( GRU, 1, OpSchema() .SetDoc(GRU_ver1_doc) .Attr( "activations", "A list of 2 (or 4 if bidirectional) activation functions " "for update, reset, and hidden gates. The activation functions must be one " "of the activation functions specified above. Optional: See the equations " "for default if not specified.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Input( 1, "W", "The weight tensor for the gates. Concatenation of `W[zrh]` and `WB[zrh]` " "(if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 3*hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `R[zrh]` and `RB[zrh]` " "(if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 3*hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for the gates. Concatenation of `[Wb[zrh], Rb[zrh]]` and " "`[WBb[zrh], RBb[zrh]]` (if bidirectional) along dimension 0. This tensor " "has shape `[num_directions, 6*hidden_size]`. Optional: If not specified " "- assumed to be 0", "T", OpSchema::Optional) .FillUsing(RNNDocGeneratorOld("GRU"))); // Versions 1 to 6 of RNN/LSTM and versions 3 to 6 of GRU: void RNNShapeInference1(InferenceContext& ctx) { TensorShapeProto::Dimension num_directions, seq_length, batch_size, hidden_size; auto direction = getAttribute(ctx, "direction", "forward"); if ((direction == "forward") || (direction == "reverse")) num_directions.set_dim_value(1); else if (direction == "bidirectional") num_directions.set_dim_value(2); // else leave num_directions unknown in case of incorrect attribute value auto hidden_size_value = getAttribute(ctx, "hidden_size", -1); if (hidden_size_value > 0) hidden_size.set_dim_value(hidden_size_value); if (hasInputShape(ctx, 0)) { auto& first_input_shape = getInputShape(ctx, 0); seq_length = first_input_shape.dim(0); batch_size = first_input_shape.dim(1); } // The treatment of outputs is a bit complicated because of the combination of // optional outputs and the output_sequence attribute. bool output_sequence = (getAttribute(ctx, "output_sequence", 0) != 0); auto num_outputs = ctx.getNumOutputs(); if (num_outputs == 0) return; // Unlikely, but seems legal. propagateElemTypeFromInputToOutput(ctx, 0, 0); if (num_outputs > 1) propagateElemTypeFromInputToOutput(ctx, 0, 1); if (num_outputs > 2) propagateElemTypeFromInputToOutput(ctx, 0, 2); if (output_sequence) { // No ambiguity in spec updateOutputShape( ctx, 0, {seq_length, num_directions, batch_size, hidden_size}); // Y if (num_outputs > 1) updateOutputShape( ctx, 1, {num_directions, batch_size, hidden_size}); // Y_h if (num_outputs > 2) updateOutputShape( ctx, 2, {num_directions, batch_size, hidden_size}); // Y_c } else { // Documentation suggests that the output Y is absent in this case // Different tests seem to disagree on whether Y_h and Y_c, if present, // should be in positions 0 & 1 or 1 & 2. updateOutputShape(ctx, 0, // {num_directions, batch_size, hidden_size}); // Y_h if (num_outputs > 1) // updateOutputShape(ctx, 1, {num_directions, batch_size, hidden_size}); // // Y_c } } std::function RNNDocGenerator1(const char* /*name*/) { return [=](OpSchema& schema) { schema.Attr( "direction", "Specify if the RNN is forward, reverse, or bidirectional. " "Must be one of forward (default), reverse, or bidirectional.", AttributeProto::STRING, std::string("forward")); schema.Attr( "hidden_size", "Number of neurons in the hidden layer", AttributeProto::INT, OPTIONAL_VALUE); schema.Attr( "activation_alpha", "Optional scaling values used by some activation functions. The values " "are consumed in the order of activation functions, for example (f, g, h) " "in LSTM. Default values are the same as of corresponding ONNX operators." "For example with LeakyRelu, the default alpha is 0.01.", AttributeProto::FLOATS, OPTIONAL_VALUE); schema.Attr( "activation_beta", "Optional scaling values used by some activation functions. The values " "are consumed in the order of activation functions, for example (f, g, h) " "in LSTM. Default values are the same as of corresponding ONNX operators.", AttributeProto::FLOATS, OPTIONAL_VALUE); schema.Attr( "output_sequence", "The sequence output for the hidden is optional if 0. Default 0.", AttributeProto::INT, static_cast(0)); schema.Attr( "clip", "Cell clip threshold. Clipping bounds the elements of a tensor " "in the range of [-threshold, +threshold] and is applied to the input " "of activations. No clip if not specified.", AttributeProto::FLOAT, OPTIONAL_VALUE); schema.Input( 0, "X", "The input sequences packed (and potentially padded) into one 3-D " "tensor with the shape of `[seq_length, batch_size, input_size]`.", "T"); schema.Input( 4, "sequence_lens", "Optional tensor specifying lengths of the sequences in a batch. " "If not specified - assumed all sequences in the batch to have " "length `seq_length`. It has shape `[batch_size]`.", "T1", OpSchema::Optional); schema.Input( 5, "initial_h", "Optional initial value of the hidden. If not specified - assumed " "to be 0. It has shape `[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional); schema.Output( 0, "Y", "A tensor that concats all the intermediate output values of the hidden. " "It has shape `[seq_length, num_directions, batch_size, hidden_size]`. " "It is optional if `output_sequence` is 0.", "T", OpSchema::Optional); schema.Output( 1, "Y_h", "The last output value of the hidden. It has shape " "`[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeConstraint( "T1", {"tensor(int32)"}, "Constrain seq_lens to integer tensor."); schema.TypeAndShapeInferenceFunction(RNNShapeInference1); }; } static const char* RNN_ver1_doc = R"DOC( Computes an one-layer simple RNN. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `i` - input gate `t` - time step (t-1 means previous time step) `Wi` - W parameter weight matrix for input gate `Ri` - R recurrence weight matrix for input gate `Wbi` - W parameter bias vector for input gate `Rbi` - R parameter bias vector for input gate `WBi` - W parameter weight matrix for backward input gate `RBi` - R recurrence weight matrix for backward input gate `WBbi` - WR bias vectors for backward input gate `RBbi` - RR bias vectors for backward input gate `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Tanh): - Ht = f(Xt*(Wi^T) + Ht-1*Ri + Wbi + Rbi) )DOC"; ONNX_OPERATOR_SET_SCHEMA( RNN, 1, OpSchema() .SetDoc(RNN_ver1_doc) .Attr( "activations", "One (or two if bidirectional) activation function for " "input gate. The activation function must be one of the activation " "functions specified above. Optional: Default `Tanh` if not specified.", AttributeProto::STRINGS, std::vector{"Tanh", "Tanh"}) .Input( 1, "W", "The weight tensor for input gate. Concatenation of `Wi` and `WBi` " "(if bidirectional). The tensor has shape " "`[num_directions, hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `Ri` and `RBi` " "(if bidirectional). The tensor has shape " "`[num_directions, hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for input gate. Concatenation of `[Wbi, Rbi]` " "and `[WBbi, RBbi]` (if bidirectional). The tensor has shape " "`[num_directions, 2*hidden_size]`. Optional: If not specified - assumed " "to be 0.", "T", OpSchema::Optional) .FillUsing(RNNDocGenerator1("RNN"))); static const char* GRU_ver3_doc = R"DOC( Computes an one-layer GRU. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `z` - update gate `r` - reset gate `h` - hidden gate `t` - time step (t-1 means previous time step) `W[zrh]` - W parameter weight matrix for update, reset, and hidden gates `R[zrh]` - R recurrence weight matrix for update, reset, and hidden gates `Wb[zrh]` - W bias vectors for update, reset, and hidden gates `Rb[zrh]` - R bias vectors for update, reset, and hidden gates `WB[zrh]` - W parameter weight matrix for backward update, reset, and hidden gates `RB[zrh]` - R recurrence weight matrix for backward update, reset, and hidden gates `WBb[zrh]` - W bias vectors for backward update, reset, and hidden gates `RBb[zrh]` - R bias vectors for backward update, reset, and hidden gates `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Sigmoid, g=Tanh): - zt = f(Xt*(Wz^T) + Ht-1*Rz + Wbz + Rbz) - rt = f(Xt*(Wr^T) + Ht-1*Rr + Wbr + Rbr) - ht = g(Xt*(Wh^T) + (rt (.) Ht-1)*Rh + Rbh + Wbh) # default, when linear_before_reset = 0 - ht = g(Xt*(Wh^T) + (rt (.) (Ht-1*Rh + Rbh) + Wbh) # when linear_before_reset != 0 - Ht = (1 - zt) (.) ht + zt (.) Ht-1 )DOC"; ONNX_OPERATOR_SET_SCHEMA( GRU, 3, OpSchema() .SetDoc(GRU_ver3_doc) .Attr( "activations", "A list of 2 (or 4 if bidirectional) activation functions " "for update, reset, and hidden gates. The activation functions must be one " "of the activation functions specified above. Optional: See the equations " "for default if not specified.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "linear_before_reset", "When computing the output of the hidden gate, " "apply the linear transformation before multiplying by the output of the " "reset gate.", AttributeProto::INT, static_cast(0)) .Input( 1, "W", "The weight tensor for the gates. Concatenation of `W[zrh]` and `WB[zrh]` " "(if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 3*hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `R[zrh]` and `RB[zrh]` " "(if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 3*hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for the gates. Concatenation of `[Wb[zrh], Rb[zrh]]` and " "`[WBb[zrh], RBb[zrh]]` (if bidirectional) along dimension 0. This tensor " "has shape `[num_directions, 6*hidden_size]`. Optional: If not specified " "- assumed to be 0", "T", OpSchema::Optional) .FillUsing(RNNDocGenerator1("GRU"))); static const char* LSTM_ver1_doc = R"DOC( Computes an one-layer LSTM. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `i` - input gate `o` - output gate `f` - forget gate `c` - cell gate `t` - time step (t-1 means previous time step) `W[iofc]` - W parameter weight matrix for input, output, forget, and cell gates `R[iofc]` - R recurrence weight matrix for input, output, forget, and cell gates `Wb[iofc]` - W bias vectors for input, output, forget, and cell gates `Rb[iofc]` - R bias vectors for input, output, forget, and cell gates `P[iof]` - P peephole weight vector for input, output, and forget gates `WB[iofc]` - W parameter weight matrix for backward input, output, forget, and cell gates `RB[iofc]` - R recurrence weight matrix for backward input, output, forget, and cell gates `WBb[iofc]` - W bias vectors for backward input, output, forget, and cell gates `RBb[iofc]` - R bias vectors for backward input, output, forget, and cell gates `PB[iof]` - P peephole weight vector for backward input, output, and forget gates `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Sigmoid, g=Tanh, h=Tanh): - it = f(Xt*(Wi^T) + Ht-1*Ri + Pi (.) Ct-1 + Wbi + Rbi) - ft = f(Xt*(Wf^T) + Ht-1*Rf + Pf (.) Ct-1 + Wbf + Rbf) - ct = g(Xt*(Wc^T) + Ht-1*Rc + Wbc + Rbc) - Ct = ft (.) Ct-1 + it (.) ct - ot = f(Xt*(Wo^T) + Ht-1*Ro + Po (.) Ct + Wbo + Rbo) - Ht = ot (.) h(Ct) )DOC"; ONNX_OPERATOR_SET_SCHEMA( LSTM, 1, OpSchema() .SetDoc(LSTM_ver1_doc) .Attr( "activations", "A list of 3 (or 6 if bidirectional) activation functions " "for input, output, forget, cell, and hidden. The activation functions must " "be one of the activation functions specified above. Optional: See the equations " "for default if not specified.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "input_forget", "Couple the input and forget gates if 1, default 0.", AttributeProto::INT, static_cast(0)) .Input( 1, "W", "The weight tensor for the gates. Concatenation of `W[iofc]` and " "`WB[iofc]` (if bidirectional) along dimension 0. The tensor has shape " "`[num_directions, 4*hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `R[iofc]` and " "`RB[iofc]` (if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 4*hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for input gate. Concatenation of `[Wb[iofc], Rb[iofc]]`, " "and `[WBb[iofc], RBb[iofc]]` (if bidirectional) along dimension 0. This " "tensor has shape `[num_directions, 8*hidden_size]`. Optional: If not " "specified - assumed to be 0.", "T", OpSchema::Optional) .Input( 6, "initial_c", "Optional initial value of the cell. If not specified - assumed " "to be 0. It has shape `[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional) .Input( 7, "P", "The weight tensor for peepholes. Concatenation of `P[iof]` and " "`PB[iof]` (if bidirectional) along dimension 0. It has shape " "`[num_directions, 3*hidde_size]`. Optional: If not specified - " "assumed to be 0.", "T", OpSchema::Optional) .FillUsing(RNNDocGenerator1("LSTM")) .Output( 2, "Y_c", "The last output value of the cell. It has shape " "`[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional)); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/rnn/defs.cc0000664000000000000000000003745313655345213015154 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { void RNNShapeInference(InferenceContext& ctx) { TensorShapeProto::Dimension num_directions, seq_length, batch_size, hidden_size; auto direction = getAttribute(ctx, "direction", "forward"); if ((direction == "forward") || (direction == "reverse")) num_directions.set_dim_value(1); else if (direction == "bidirectional") num_directions.set_dim_value(2); // else leave num_directions unknown in case of incorrect attribute value auto hidden_size_value = getAttribute(ctx, "hidden_size", -1); if (hidden_size_value > 0) hidden_size.set_dim_value(hidden_size_value); if (hasInputShape(ctx, 0)) { auto& first_input_shape = getInputShape(ctx, 0); if (first_input_shape.dim_size() != 3) { fail_shape_inference("First input tensor must have rank 3"); } seq_length = first_input_shape.dim(0); batch_size = first_input_shape.dim(1); } auto num_outputs = ctx.getNumOutputs(); if (num_outputs > 0) { // Y propagateElemTypeFromInputToOutput(ctx, 0, 0); updateOutputShape( ctx, 0, {seq_length, num_directions, batch_size, hidden_size}); } if (num_outputs > 1) { // Y_h propagateElemTypeFromInputToOutput(ctx, 0, 1); updateOutputShape(ctx, 1, {num_directions, batch_size, hidden_size}); } if (num_outputs > 2) { // Y_c : only in the case of LSTM propagateElemTypeFromInputToOutput(ctx, 0, 2); updateOutputShape(ctx, 2, {num_directions, batch_size, hidden_size}); } } std::function RNNDocGenerator(const char* /*name*/) { return [=](OpSchema& schema) { schema.Attr( "direction", "Specify if the RNN is forward, reverse, or bidirectional. " "Must be one of forward (default), reverse, or bidirectional.", AttributeProto::STRING, std::string("forward")); schema.Attr( "hidden_size", "Number of neurons in the hidden layer", AttributeProto::INT, OPTIONAL_VALUE); schema.Attr( "activation_alpha", "Optional scaling values used by some activation functions. The values " "are consumed in the order of activation functions, for example (f, g, h) " "in LSTM. Default values are the same as of corresponding ONNX operators." "For example with LeakyRelu, the default alpha is 0.01.", AttributeProto::FLOATS, OPTIONAL_VALUE); schema.Attr( "activation_beta", "Optional scaling values used by some activation functions. The values " "are consumed in the order of activation functions, for example (f, g, h) " "in LSTM. Default values are the same as of corresponding ONNX operators.", AttributeProto::FLOATS, OPTIONAL_VALUE); schema.Attr( "clip", "Cell clip threshold. Clipping bounds the elements of a tensor " "in the range of [-threshold, +threshold] and is applied to the input " "of activations. No clip if not specified.", AttributeProto::FLOAT, OPTIONAL_VALUE); schema.Input( 0, "X", "The input sequences packed (and potentially padded) into one 3-D " "tensor with the shape of `[seq_length, batch_size, input_size]`.", "T"); schema.Input( 4, "sequence_lens", "Optional tensor specifying lengths of the sequences in a batch. " "If not specified - assumed all sequences in the batch to have " "length `seq_length`. It has shape `[batch_size]`.", "T1", OpSchema::Optional); schema.Input( 5, "initial_h", "Optional initial value of the hidden. If not specified - assumed " "to be 0. It has shape `[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional); schema.Output( 0, "Y", "A tensor that concats all the intermediate output values of the hidden. " "It has shape `[seq_length, num_directions, batch_size, hidden_size]`. ", "T", OpSchema::Optional); schema.Output( 1, "Y_h", "The last output value of the hidden. It has shape " "`[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional); schema.TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrain input and output types to float tensors."); schema.TypeConstraint( "T1", {"tensor(int32)"}, "Constrain seq_lens to integer tensor."); schema.TypeAndShapeInferenceFunction(RNNShapeInference); }; } static const char* RNN_ver7_doc = R"DOC( Computes an one-layer simple RNN. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `i` - input gate `t` - time step (t-1 means previous time step) `Wi` - W parameter weight matrix for input gate `Ri` - R recurrence weight matrix for input gate `Wbi` - W parameter bias vector for input gate `Rbi` - R parameter bias vector for input gate `WBi` - W parameter weight matrix for backward input gate `RBi` - R recurrence weight matrix for backward input gate `WBbi` - WR bias vectors for backward input gate `RBbi` - RR bias vectors for backward input gate `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Tanh): - Ht = f(Xt*(Wi^T) + Ht-1*(Ri^T) + Wbi + Rbi) )DOC"; ONNX_OPERATOR_SET_SCHEMA( RNN, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(RNN_ver7_doc) + GenerateOptionalArgumentsDoc())) .Attr( "activations", "One (or two if bidirectional) activation function for " "input gate. The activation function must be one of the activation " "functions specified above. Optional: Default `Tanh` if not specified.", AttributeProto::STRINGS, std::vector{"Tanh", "Tanh"}) .Input( 1, "W", "The weight tensor for input gate. Concatenation of `Wi` and `WBi` " "(if bidirectional). The tensor has shape " "`[num_directions, hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `Ri` and `RBi` " "(if bidirectional). The tensor has shape " "`[num_directions, hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for input gate. Concatenation of `[Wbi, Rbi]` " "and `[WBbi, RBbi]` (if bidirectional). The tensor has shape " "`[num_directions, 2*hidden_size]`. Optional: If not specified - assumed " "to be 0.", "T", OpSchema::Optional) .FillUsing(RNNDocGenerator("RNN"))); static const char* GRU_ver7_doc = R"DOC( Computes an one-layer GRU. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `z` - update gate `r` - reset gate `h` - hidden gate `t` - time step (t-1 means previous time step) `W[zrh]` - W parameter weight matrix for update, reset, and hidden gates `R[zrh]` - R recurrence weight matrix for update, reset, and hidden gates `Wb[zrh]` - W bias vectors for update, reset, and hidden gates `Rb[zrh]` - R bias vectors for update, reset, and hidden gates `WB[zrh]` - W parameter weight matrix for backward update, reset, and hidden gates `RB[zrh]` - R recurrence weight matrix for backward update, reset, and hidden gates `WBb[zrh]` - W bias vectors for backward update, reset, and hidden gates `RBb[zrh]` - R bias vectors for backward update, reset, and hidden gates `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Sigmoid, g=Tanh): - zt = f(Xt*(Wz^T) + Ht-1*(Rz^T) + Wbz + Rbz) - rt = f(Xt*(Wr^T) + Ht-1*(Rr^T) + Wbr + Rbr) - ht = g(Xt*(Wh^T) + (rt (.) Ht-1)*(Rh^T) + Rbh + Wbh) # default, when linear_before_reset = 0 - ht = g(Xt*(Wh^T) + (rt (.) (Ht-1*(Rh^T) + Rbh)) + Wbh) # when linear_before_reset != 0 - Ht = (1 - zt) (.) ht + zt (.) Ht-1 )DOC"; ONNX_OPERATOR_SET_SCHEMA( GRU, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(GRU_ver7_doc) + GenerateOptionalArgumentsDoc())) .Attr( "activations", "A list of 2 (or 4 if bidirectional) activation functions " "for update, reset, and hidden gates. The activation functions must be one " "of the activation functions specified above. Optional: See the equations " "for default if not specified.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "linear_before_reset", "When computing the output of the hidden gate, " "apply the linear transformation before multiplying by the output of the " "reset gate.", AttributeProto::INT, static_cast(0)) .Input( 1, "W", "The weight tensor for the gates. Concatenation of `W[zrh]` and `WB[zrh]` " "(if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 3*hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `R[zrh]` and `RB[zrh]` " "(if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 3*hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for the gates. Concatenation of `[Wb[zrh], Rb[zrh]]` and " "`[WBb[zrh], RBb[zrh]]` (if bidirectional) along dimension 0. This tensor " "has shape `[num_directions, 6*hidden_size]`. Optional: If not specified " "- assumed to be 0", "T", OpSchema::Optional) .FillUsing(RNNDocGenerator("GRU"))); static const char* LSTM_ver7_doc = R"DOC( Computes an one-layer LSTM. This operator is usually supported via some custom implementation such as CuDNN. Notations: `X` - input tensor `i` - input gate `o` - output gate `f` - forget gate `c` - cell gate `t` - time step (t-1 means previous time step) `W[iofc]` - W parameter weight matrix for input, output, forget, and cell gates `R[iofc]` - R recurrence weight matrix for input, output, forget, and cell gates `Wb[iofc]` - W bias vectors for input, output, forget, and cell gates `Rb[iofc]` - R bias vectors for input, output, forget, and cell gates `P[iof]` - P peephole weight vector for input, output, and forget gates `WB[iofc]` - W parameter weight matrix for backward input, output, forget, and cell gates `RB[iofc]` - R recurrence weight matrix for backward input, output, forget, and cell gates `WBb[iofc]` - W bias vectors for backward input, output, forget, and cell gates `RBb[iofc]` - R bias vectors for backward input, output, forget, and cell gates `PB[iof]` - P peephole weight vector for backward input, output, and forget gates `H` - Hidden state `num_directions` - 2 if direction == bidirectional else 1 Activation functions: Relu(x) - max(0, x) Tanh(x) - (1 - e^{-2x})/(1 + e^{-2x}) Sigmoid(x) - 1/(1 + e^{-x}) (NOTE: Below are optional) Affine(x) - alpha*x + beta LeakyRelu(x) - x if x >= 0 else alpha * x ThresholdedRelu(x) - x if x >= alpha else 0 ScaledTanh(x) - alpha*Tanh(beta*x) HardSigmoid(x) - min(max(alpha*x + beta, 0), 1) Elu(x) - x if x >= 0 else alpha*(e^x - 1) Softsign(x) - x/(1 + |x|) Softplus(x) - log(1 + e^x) Equations (Default: f=Sigmoid, g=Tanh, h=Tanh): - it = f(Xt*(Wi^T) + Ht-1*(Ri^T) + Pi (.) Ct-1 + Wbi + Rbi) - ft = f(Xt*(Wf^T) + Ht-1*(Rf^T) + Pf (.) Ct-1 + Wbf + Rbf) - ct = g(Xt*(Wc^T) + Ht-1*(Rc^T) + Wbc + Rbc) - Ct = ft (.) Ct-1 + it (.) ct - ot = f(Xt*(Wo^T) + Ht-1*(Ro^T) + Po (.) Ct + Wbo + Rbo) - Ht = ot (.) h(Ct) )DOC"; ONNX_OPERATOR_SET_SCHEMA( LSTM, 7, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(LSTM_ver7_doc) + GenerateOptionalArgumentsDoc())) .Attr( "activations", "A list of 3 (or 6 if bidirectional) activation functions " "for input, output, forget, cell, and hidden. The activation functions must " "be one of the activation functions specified above. Optional: See the equations " "for default if not specified.", AttributeProto::STRINGS, OPTIONAL_VALUE) .Attr( "input_forget", "Couple the input and forget gates if 1.", AttributeProto::INT, static_cast(0)) .Input( 1, "W", "The weight tensor for the gates. Concatenation of `W[iofc]` and " "`WB[iofc]` (if bidirectional) along dimension 0. The tensor has shape " "`[num_directions, 4*hidden_size, input_size]`.", "T") .Input( 2, "R", "The recurrence weight tensor. Concatenation of `R[iofc]` and " "`RB[iofc]` (if bidirectional) along dimension 0. This tensor has shape " "`[num_directions, 4*hidden_size, hidden_size]`.", "T") .Input( 3, "B", "The bias tensor for input gate. Concatenation of `[Wb[iofc], Rb[iofc]]`, " "and `[WBb[iofc], RBb[iofc]]` (if bidirectional) along dimension 0. This " "tensor has shape `[num_directions, 8*hidden_size]`. Optional: If not " "specified - assumed to be 0.", "T", OpSchema::Optional) .Input( 6, "initial_c", "Optional initial value of the cell. If not specified - assumed " "to be 0. It has shape `[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional) .Input( 7, "P", "The weight tensor for peepholes. Concatenation of `P[iof]` and " "`PB[iof]` (if bidirectional) along dimension 0. It has shape " "`[num_directions, 3*hidde_size]`. Optional: If not specified - " "assumed to be 0.", "T", OpSchema::Optional) .FillUsing(RNNDocGenerator("LSTM")) .Output( 2, "Y_c", "The last output value of the cell. It has shape " "`[num_directions, batch_size, hidden_size]`.", "T", OpSchema::Optional)); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/tensor_proto_util.cc0000664000000000000000000001170413655345213017217 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "tensor_proto_util.h" #include #include "onnx/common/platform_helpers.h" namespace ONNX_NAMESPACE { #define DEFINE_TO_TENSOR_ONE(type, enumType, field) \ template <> \ TensorProto ToTensor(const type& value) { \ TensorProto t; \ t.set_data_type(enumType); \ t.add_##field##_data(value); \ return t; \ } #define DEFINE_TO_TENSOR_LIST(type, enumType, field) \ template <> \ TensorProto ToTensor(const std::vector& values) { \ TensorProto t; \ t.clear_##field##_data(); \ t.set_data_type(enumType); \ for (const type& val : values) { \ t.add_##field##_data(val); \ } \ return t; \ } #define DEFINE_PARSE_DATA(type, typed_data_fetch) \ template <> \ const std::vector ParseData(const TensorProto* tensor_proto) { \ std::vector res; \ if (!tensor_proto->has_raw_data()) { \ const auto& data = tensor_proto->typed_data_fetch(); \ res.insert(res.end(), data.begin(), data.end()); \ return res; \ } \ /* make copy as we may have to reverse bytes */ \ std::string raw_data = tensor_proto->raw_data(); \ /* okay to remove const qualifier as we have already made a copy */ \ char* bytes = const_cast(raw_data.c_str()); \ /*onnx is little endian serialized always-tweak byte order if needed*/ \ if (!is_processor_little_endian()) { \ const size_t element_size = sizeof(type); \ const size_t num_elements = raw_data.size() / element_size; \ for (size_t i = 0; i < num_elements; ++i) { \ char* start_byte = bytes + i * element_size; \ char* end_byte = start_byte + element_size - 1; \ /* keep swapping */ \ for (size_t count = 0; count < element_size / 2; ++count) { \ char temp = *start_byte; \ *start_byte = *end_byte; \ *end_byte = temp; \ ++start_byte; \ --end_byte; \ } \ } \ } \ res.insert( \ res.end(), \ reinterpret_cast(bytes), \ reinterpret_cast(bytes + raw_data.size())); \ return res; \ } DEFINE_TO_TENSOR_ONE(float, TensorProto_DataType_FLOAT, float) DEFINE_TO_TENSOR_ONE(bool, TensorProto_DataType_BOOL, int32) DEFINE_TO_TENSOR_ONE(int32_t, TensorProto_DataType_INT32, int32) DEFINE_TO_TENSOR_ONE(int64_t, TensorProto_DataType_INT64, int64) DEFINE_TO_TENSOR_ONE(uint64_t, TensorProto_DataType_UINT64, uint64) DEFINE_TO_TENSOR_ONE(double, TensorProto_DataType_DOUBLE, double) DEFINE_TO_TENSOR_ONE(std::string, TensorProto_DataType_STRING, string) DEFINE_TO_TENSOR_LIST(float, TensorProto_DataType_FLOAT, float) DEFINE_TO_TENSOR_LIST(bool, TensorProto_DataType_BOOL, int32) DEFINE_TO_TENSOR_LIST(int32_t, TensorProto_DataType_INT32, int32) DEFINE_TO_TENSOR_LIST(int64_t, TensorProto_DataType_INT64, int64) DEFINE_TO_TENSOR_LIST(uint64_t, TensorProto_DataType_UINT64, uint64) DEFINE_TO_TENSOR_LIST(double, TensorProto_DataType_DOUBLE, double) DEFINE_TO_TENSOR_LIST(std::string, TensorProto_DataType_STRING, string) DEFINE_PARSE_DATA(int32_t, int32_data) DEFINE_PARSE_DATA(int64_t, int64_data) DEFINE_PARSE_DATA(float, float_data) DEFINE_PARSE_DATA(double, double_data) } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/function.cc0000664000000000000000000001022313655345213015245 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/function.h" #include "onnx/defs/schema.h" #include "onnx/string_utils.h" namespace ONNX_NAMESPACE { std::string InteralTensorNameGenerator( const std::string& node_name, const std::string& internal_name) { std::string new_name = "Func_" + node_name + internal_name; return new_name; } void FunctionExpandHelper( const NodeProto& node, const FunctionProto& func, GraphProto& g, const std::string& node_prefix) { // Create a temporary unique node prefix for tensor names std::string uniq_prefix = node_prefix; if (uniq_prefix.empty()) { const void* address = static_cast(&node); std::stringstream ss; ss << address; uniq_prefix = ss.str(); } std::string node_name = node.has_name() ? node.name() : func.name() + uniq_prefix; std::unordered_map io_names_map; std::unordered_map attr_map; for (int idx = 0; idx < node.input_size(); ++idx) { if (idx >= func.input_size()) { throw std::runtime_error( "Input for function node " + node_name + " is out of bounds"); } io_names_map[func.input().Get(idx)] = node.input().Get(idx); } for (int idx = 0; idx < node.output_size(); ++idx) { if (idx >= func.output_size()) { throw std::runtime_error( "Output for function node " + node_name + " is out of bounds"); } // If the node output is missing, the corresponding function output should // be treated as an internal value (not as missing) because it could also be // an intermediate value. if (node.output().Get(idx) == "") { continue; } io_names_map[func.output().Get(idx)] = node.output().Get(idx); } for (auto& attr : node.attribute()) { attr_map[attr.name()] = attr; } // For undefined attributes of the function node // add default values obtained from the function schema. const OpSchemaRegistry* schema_registry = OpSchemaRegistry::Instance(); const auto schema = schema_registry->GetSchema( node.op_type(), func.since_version(), node.domain()); std::map default_attrs = schema->attributes(); for (const auto& pair : default_attrs) { const auto& attr_name = pair.first; const auto& attr = pair.second; if (!attr_map.count(attr_name)) { attr_map[attr_name] = attr.default_value; } } for (auto& function_node : func.node()) { NodeProto* new_node = g.add_node(); new_node->CopyFrom(function_node); new_node->clear_input(); new_node->clear_output(); new_node->clear_attribute(); for (auto& input : function_node.input()) { if (io_names_map.count(input)) { new_node->add_input(io_names_map[input]); } else { new_node->add_input(InteralTensorNameGenerator(node_name, input)); } } for (auto& output : function_node.output()) { if (io_names_map.count(output)) { new_node->add_output(io_names_map[output]); } else { new_node->add_output(InteralTensorNameGenerator(node_name, output)); } } for (auto& attr : function_node.attribute()) { if (attr.has_ref_attr_name()) { if (attr_map.count(attr.ref_attr_name())) { AttributeProto* new_attr = new_node->add_attribute(); new_attr->CopyFrom(attr_map[attr.ref_attr_name()]); new_attr->set_name(attr.name()); } } else { AttributeProto* new_attr = new_node->add_attribute(); new_attr->CopyFrom(attr); } } } } std::vector FunctionBodyHelper::BuildNodes( const std::vector& node_defs) { std::vector nodes(node_defs.size()); for (size_t i = 0; i < node_defs.size(); i++) { const NodeDef& node = node_defs[i]; NodeProto& n = nodes[i]; n.set_op_type(node.op_type); for (const auto& i : node.inputs) { n.add_input(i); } for (const auto& o : node.outputs) { n.add_output(o); } for (const auto& attr : node.attributes) { *(n.add_attribute()) = attr.proto; } } return nodes; } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/tensor_util.h0000664000000000000000000000040213655345213015627 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #pragma once #include "onnx/common/ir.h" namespace ONNX_NAMESPACE { template const std::vector ParseData(const Tensor* tensor); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/logical/0000775000000000000000000000000013655345213014525 5ustar rootrootonnx-1.7.0/onnx/defs/logical/defs.cc0000664000000000000000000002003613655345213015756 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" #include "onnx/defs/function.h" namespace ONNX_NAMESPACE { inline void unaryLogicalOpInference(InferenceContext& ctx) { // Type inference updateOutputElemType(ctx, 0, TensorProto::BOOL); // Shape inference if (hasInputShape(ctx, 0)) { propagateShapeFromInputToOutput(ctx, 0, 0); } } std::function BinaryLogicDocGenerator(const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( Returns the tensor resulted from performing the `{name}` logical operation elementwise on the input tensors `A` and `B` (with Numpy-style broadcasting support). {broadcast_doc} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll( doc, "{broadcast_doc}", GenerateBroadcastingDocMul().c_str());); schema.SetDoc(doc); schema.Input(0, "A", "First input operand for the logical operator.", "T"); schema.Input(1, "B", "Second input operand for the logical operator.", "T"); schema.Output(0, "C", "Result tensor.", "T1"); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference updateOutputElemType(ctx, 0, TensorProto::BOOL); // Shape inference if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); }); }; } ONNX_OPERATOR_SET_SCHEMA( And, 7, OpSchema() .FillUsing(BinaryLogicDocGenerator("and")) .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input to boolean tensor.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Or, 7, OpSchema() .FillUsing(BinaryLogicDocGenerator("or")) .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input to boolean tensor.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Xor, 7, OpSchema() .FillUsing(BinaryLogicDocGenerator("xor")) .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input to boolean tensor.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Greater, 9, OpSchema() .FillUsing(BinaryLogicDocGenerator("greater")) .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrains input types to all numeric tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Less, 9, OpSchema() .FillUsing(BinaryLogicDocGenerator("less")) .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrains input types to all numeric tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Equal, 11, OpSchema() .FillUsing(BinaryLogicDocGenerator("equal")) .TypeConstraint( "T", {"tensor(bool)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)", "tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrains input types to all numeric tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); static const char* Not_ver1_doc = R"DOC( Returns the negation of the input tensor element-wise. )DOC"; ONNX_OPERATOR_SET_SCHEMA( Not, 1, OpSchema() .SetDoc(Not_ver1_doc) .Input(0, "X", "Input tensor", "T") .Output(0, "Y", "Output tensor", "T") .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input/output to boolean tensors.") .TypeAndShapeInferenceFunction(unaryLogicalOpInference)); static const char* BitShift_ver11_doc = R"DOC( Bitwise shift operator performs element-wise operation. For each input element, if the attribute "direction" is "RIGHT", this operator moves its binary representation toward the right side so that the input value is effectively decreased. If the attribute "direction" is "LEFT", bits of binary representation moves toward the left side, which results the increase of its actual value. The input X is the tensor to be shifted and another input Y specifies the amounts of shifting. For example, if "direction" is "Right", X is [1, 4], and S is [1, 1], the corresponding output Z would be [0, 2]. If "direction" is "LEFT" with X=[1, 2] and S=[1, 2], the corresponding output Y would be [2, 8]. Because this operator supports Numpy-style broadcasting, X's and Y's shapes are not necessarily identical. )DOC"; ONNX_OPERATOR_SET_SCHEMA( BitShift, 11, OpSchema() .SetDoc(GET_OP_DOC_STR( std::string(BitShift_ver11_doc) + GenerateBroadcastingDocMul())) .Input(0, "X", "First operand, input to be shifted.", "T") .Input(1, "Y", "Second operand, amounts of shift.", "T") .Output(0, "Z", "Output tensor", "T") .TypeConstraint( "T", {"tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)"}, "Constrain input and output types to integer tensors.") .Attr( "direction", "Direction of moving bits. It can be either \"RIGHT\" (for right shift) " "or \"LEFT\" (for left shift).", AttributeProto::STRING) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { // Type inference propagateElemTypeFromInputToOutput(ctx, 0, 0); // Shape inference if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); })); ONNX_OPERATOR_SET_SCHEMA( LessOrEqual, 12, OpSchema() .FillUsing(BinaryLogicDocGenerator("less_equal")) .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrains input types to all numeric tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.") .TypeAndShapeInferenceFunction(InferenceFunction()) .FunctionBody(FunctionBodyHelper::BuildNodes( {// nodes: {outputs, op, inputs, attributes} {{"O1"}, "Less", {"A", "B"}}, {{"O2"}, "Equal", {"A", "B"}}, {{"C"}, "Or", {"O1", "O2"}}}))); ONNX_OPERATOR_SET_SCHEMA( GreaterOrEqual, 12, OpSchema() .FillUsing(BinaryLogicDocGenerator("greater_equal")) .TypeConstraint( "T", OpSchema::all_numeric_types(), "Constrains input types to all numeric tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.") .TypeAndShapeInferenceFunction(InferenceFunction()) .FunctionBody(FunctionBodyHelper::BuildNodes( {// nodes: {outputs, op, inputs, attributes} {{"O1"}, "Greater", {"A", "B"}}, {{"O2"}, "Equal", {"A", "B"}}, {{"C"}, "Or", {"O1", "O2"}}}))); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/logical/old.cc0000664000000000000000000001414213655345213015614 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" using namespace ONNX_NAMESPACE; namespace ONNX_NAMESPACE { inline void logicalOpInference_opset1(InferenceContext& ctx) { updateOutputElemType(ctx, 0, TensorProto::BOOL); if (hasInputShape(ctx, 0)) { propagateShapeFromInputToOutput(ctx, 0, 0); } } std::function BinaryLogicDocGenerator_opset1( const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR(doc = R"DOC( Returns the tensor resulted from performing the `{name}` logical operation elementwise on the input tensors `A` and `B`. If broadcasting is enabled, the right-hand-side argument will be broadcasted to match the shape of left-hand-side argument. See the doc of `Add` for a detailed description of the broadcasting rules. )DOC"; ReplaceAll(doc, "{name}", name);); schema.SetDoc(doc); schema.Attr( "broadcast", "Enable broadcasting", AttributeProto::INT, static_cast(0)); schema.Attr( "axis", "If set, defines the broadcast dimensions.", AttributeProto::INT, OPTIONAL_VALUE); schema.Input(0, "A", "Left input tensor for the logical operator.", "T"); schema.Input(1, "B", "Right input tensor for the logical operator.", "T"); schema.Output(0, "C", "Result tensor.", "T1"); schema.TypeAndShapeInferenceFunction(logicalOpInference_opset1); }; } std::function BinaryLogicDocGenerator_opset7( const char* name) { return [=](OpSchema& schema) { std::string doc; POPULATE_OP_DOC_STR( doc = R"DOC( Returns the tensor resulted from performing the `{name}` logical operation elementwise on the input tensors `A` and `B` (with Numpy-style broadcasting support). {broadcast_doc} )DOC"; ReplaceAll(doc, "{name}", name); ReplaceAll( doc, "{broadcast_doc}", GenerateBroadcastingDocMul().c_str());); schema.SetDoc(doc); schema.Input(0, "A", "First input operand for the logical operator.", "T"); schema.Input(1, "B", "Second input operand for the logical operator.", "T"); schema.Output(0, "C", "Result tensor.", "T1"); schema.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { updateOutputElemType(ctx, 0, TensorProto::BOOL); if (hasNInputShapes(ctx, 2)) bidirectionalBroadcastShapeInference( ctx.getInputType(0)->tensor_type().shape(), ctx.getInputType(1)->tensor_type().shape(), *ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape()); }); }; } ONNX_OPERATOR_SET_SCHEMA( And, 1, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset1("and")) .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input to boolean tensor.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Or, 1, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset1("or")) .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input to boolean tensor.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Xor, 1, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset1("xor")) .TypeConstraint( "T", {"tensor(bool)"}, "Constrains input to boolean tensor.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Greater, 1, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset1("greater")) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrains input to float tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Less, 1, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset1("less")) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrains input to float tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Equal, 1, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset1("equal")) .TypeConstraint( "T", {"tensor(bool)", "tensor(int32)", "tensor(int64)"}, "Constrains input to integral tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Equal, 7, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset7("equal")) .TypeConstraint( "T", {"tensor(bool)", "tensor(int32)", "tensor(int64)"}, "Constrains input to integral tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Greater, 7, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset7("greater")) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrains input to float tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); ONNX_OPERATOR_SET_SCHEMA( Less, 7, OpSchema() .FillUsing(BinaryLogicDocGenerator_opset7("less")) .TypeConstraint( "T", {"tensor(float16)", "tensor(float)", "tensor(double)"}, "Constrains input to float tensors.") .TypeConstraint( "T1", {"tensor(bool)"}, "Constrains output to boolean tensor.")); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/controlflow/0000775000000000000000000000000013655345213015463 5ustar rootrootonnx-1.7.0/onnx/defs/controlflow/old.cc0000664000000000000000000012633613655345213016563 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { using SupportType = OpSchema::SupportType; void ScanInferenceFunctionOpset8(InferenceContext& ctx) { // NOTE: // The first input to Scan is sequence_lens. We skip that when processing // inputs in many places below, so the - 1 in multiple places is due to that. auto num_inputs = ctx.getNumInputs(); auto num_scan_inputs = narrow_cast(ctx.getAttribute("num_scan_inputs")->i()); auto num_loop_state_vars = num_inputs - 1 - num_scan_inputs; std::vector temporary_type_protos; temporary_type_protos.reserve(num_inputs); std::vector subgraph_input_types; TensorShapeProto_Dimension batch_size_dim; TensorShapeProto_Dimension sequence_len_dim; for (size_t i = 1; i < num_inputs; ++i) { bool is_loop_state_var = (i - 1) < num_loop_state_vars; bool has_shape = hasInputShape(ctx, i); const auto* input_type = ctx.getInputType(i); // Enforce type constraint for inputs if (!input_type || !input_type->has_tensor_type()) { fail_type_inference("Scan input ", i, " was not a tensor."); } if (is_loop_state_var) { // If it's a loop state variable we can propagate type and shape 1:1 to // the matching Scan output. // We can also pass through the type and shape to the subgraph but need to // remove the batch size dimension from the shape. propagateElemTypeFromInputToOutput(ctx, i, i - 1); if (has_shape) { propagateShapeFromInputToOutput(ctx, i, i - 1); // remove batch size dimension and add to subgraph_input_types temporary_type_protos.push_back( RemoveDimensionsFromShape(*input_type, 1)); subgraph_input_types.push_back(&temporary_type_protos.back()); } else { subgraph_input_types.push_back(input_type); } } else { // For other inputs there is no fixed relationships to the Scan outputs, // so we don't propagate type/shape information. // We can pass through the type and shape to the subgraph inputs but need // to remove the batch size and sequence length dimensions from the shape. if (has_shape) { // remove batch size and sequence length dimensions and add to // subgraph_input_types temporary_type_protos.push_back( RemoveDimensionsFromShape(*input_type, 2)); subgraph_input_types.push_back(&temporary_type_protos.back()); // update batch_size and sequence_len if a value is available const auto& shape = input_type->tensor_type().shape(); if (shape.dim_size() > 2) { const auto& dims = shape.dim(); mergeInDimensionInfo(dims.Get(0), batch_size_dim, 0); mergeInDimensionInfo(dims.Get(1), sequence_len_dim, 1); } } else { subgraph_input_types.push_back(input_type); } } } // Run inferencing on the subgraph std::vector output_types; GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("body"); if (graphInferencer) { std::vector input_data; for (size_t i = 1; i < num_inputs; ++i) { input_data.push_back(ctx.getInputData(i)); } output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } // if empty(), assume inferencing was skipped if (!output_types.empty()) { auto num_outputs = ctx.getNumOutputs(); if (output_types.size() != num_outputs) { fail_type_inference( "Graph attribute inferencing returned type information for ", output_types.size(), " outputs. Expected ", num_outputs); } // propagate type/shape information for loop state variables and outputs for (size_t i = 0; i < num_outputs; ++i) { const bool is_loop_state_var = i < num_loop_state_vars; auto* subgraph_output_type = output_types[i]; auto* scan_output_type = ctx.getOutputType(i); if (!subgraph_output_type->has_tensor_type()) { fail_type_inference( "Scan 'body' subgraph outputs should all be tensors but output ", i, " was not"); } // propagate output type. loop state vars were done in the above code. if (!is_loop_state_var) { scan_output_type->mutable_tensor_type()->set_elem_type( subgraph_output_type->tensor_type().elem_type()); } // propagate shape if (subgraph_output_type->tensor_type().has_shape()) { // we need to add in the batch size and sequence length values if // available before merging with any existing info. Create a copy of the // inferred type info from the subgraph to do that. TypeProto inferred_type(*subgraph_output_type); auto* mutable_inferred_tensor_type = inferred_type.mutable_tensor_type(); auto* mutable_inferred_shape = mutable_inferred_tensor_type->mutable_shape(); mutable_inferred_shape->clear_dim(); *mutable_inferred_shape->add_dim() = batch_size_dim; if (!is_loop_state_var) { *mutable_inferred_shape->add_dim() = sequence_len_dim; } for (const auto& dim : subgraph_output_type->tensor_type().shape().dim()) { (*mutable_inferred_shape->add_dim()) = dim; } auto* mutable_scan_output_tensor_type = scan_output_type->mutable_tensor_type(); mergeInShapeInfo( *mutable_inferred_tensor_type, *mutable_scan_output_tensor_type); } } } } int handle_negative_axis_validate_opset9( const std::string& attrib, int axis, int rank) { if (!(-rank <= axis && axis < rank)) fail_shape_inference( attrib, " axis value ", axis, " is invalid for a tensor of rank ", rank); return (axis >= 0 ? axis : axis + rank); } void ScanInferenceFunctionOpset9(InferenceContext& ctx) { auto num_inputs = ctx.getNumInputs(); auto num_scan_inputs = narrow_cast(ctx.getAttribute("num_scan_inputs")->i()); auto num_loop_state_vars = num_inputs - num_scan_inputs; auto num_outputs = ctx.getNumOutputs(); auto num_scan_outputs = num_outputs - num_loop_state_vars; std::vector axes, output_axes; if (getRepeatedAttribute(ctx, "scan_input_axes", axes)) { if (axes.size() != num_scan_inputs) fail_shape_inference( "Number of scan input axes specified (", axes.size(), ") is not equal to number of scan inputs (", num_scan_inputs, ")."); } else { axes.insert(axes.end(), num_scan_inputs, 0); } if (getRepeatedAttribute(ctx, "scan_output_axes", output_axes)) { if (output_axes.size() != num_scan_outputs) fail_shape_inference( "Number of scan output axes specified (", output_axes.size(), ") is not equal to number of scan outputs (", num_scan_outputs, ")."); } else { output_axes.insert(output_axes.end(), num_scan_outputs, 0); } std::vector temporary_type_protos; temporary_type_protos.reserve(num_inputs); std::vector subgraph_input_types; TensorShapeProto_Dimension sequence_len_dim; for (size_t i = 0; i < num_inputs; ++i) { bool is_loop_state_var = i < num_loop_state_vars; bool has_shape = hasInputShape(ctx, i); const auto* input_type = ctx.getInputType(i); // Enforce type constraint for inputs if (!input_type || !input_type->has_tensor_type()) { fail_type_inference("Scan input ", i, " was not a tensor."); } if (is_loop_state_var) { // If it's a loop state variable we can propagate type and shape 1:1 to // the matching Scan output. // We can also pass through the type and shape to the subgraph but need to // remove the batch size dimension from the shape. propagateElemTypeFromInputToOutput(ctx, i, i); if (has_shape) propagateShapeFromInputToOutput(ctx, i, i); subgraph_input_types.push_back(input_type); } else { // For other inputs there is no fixed relationships to the Scan outputs, // so we don't propagate type/shape information. // We can pass through the type and shape to the subgraph inputs but // need to remove the sequence length dimensions from the shape. if (has_shape) { const auto& shape = input_type->tensor_type().shape(); // remove sequence length dimensions and add to subgraph_input_types int axis = static_cast(axes[i - num_loop_state_vars]); axis = handle_negative_axis_validate_opset9( "scan_input_axes", axis, shape.dim_size()); // update sequence_len if a value is available const auto& dims = shape.dim(); mergeInDimensionInfo(dims.Get(axis), sequence_len_dim, 1); temporary_type_protos.push_back( RemoveIthDimensionFromShape(*input_type, axis)); subgraph_input_types.push_back(&temporary_type_protos.back()); } else { subgraph_input_types.push_back(input_type); } } } // Run inferencing on the subgraph std::vector output_types; GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("body"); if (graphInferencer) { std::vector input_data; for (size_t i = 0; i < num_inputs; ++i) { // ctx.getInputData(i), the input to scan, does not represent the input to // scan body. So, we pass in null, to represent an unknown value. input_data.push_back(nullptr); } output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } // if empty(), assume inferencing was skipped if (!output_types.empty()) { if (output_types.size() != num_outputs) { fail_type_inference( "Graph attribute inferencing returned type information for ", output_types.size(), " outputs. Expected ", num_outputs); } // propagate type/shape information for loop state variables and outputs for (size_t i = 0; i < num_outputs; ++i) { const bool is_loop_state_var = i < num_loop_state_vars; auto* subgraph_output_type = output_types[i]; auto* scan_output_type = ctx.getOutputType(i); auto* mutable_scan_output_tensor_type = scan_output_type->mutable_tensor_type(); if (!subgraph_output_type->has_tensor_type()) { fail_type_inference( "Scan 'body' subgraph outputs should all be tensors but output ", i, " was not"); } auto& subgraph_output_tensor_type = subgraph_output_type->tensor_type(); if (is_loop_state_var) { // merge shape; type already propagated mergeInShapeInfo( subgraph_output_tensor_type, *mutable_scan_output_tensor_type); } else { scan_output_type->mutable_tensor_type()->set_elem_type( subgraph_output_tensor_type.elem_type()); // propagate shape if (subgraph_output_tensor_type.has_shape()) { // infer shape of scan-output from the shape of scan-output-element // by adding sequence-length at the correct axis position const TensorShapeProto& subgraph_output_shape = subgraph_output_tensor_type.shape(); TensorShapeProto inferred_shape; auto subgraph_output_rank = subgraph_output_shape.dim_size(); auto output_rank = subgraph_output_rank + 1; int output_axis = static_cast(output_axes[i - num_loop_state_vars]); output_axis = handle_negative_axis_validate_opset9( "scan_output_axes", output_axis, output_rank); for (int j = 0; j < output_axis; ++j) *(inferred_shape.add_dim()) = subgraph_output_shape.dim(j); *(inferred_shape.add_dim()) = sequence_len_dim; for (int j = output_axis; j < subgraph_output_rank; ++j) *(inferred_shape.add_dim()) = subgraph_output_shape.dim(j); // Merge inferred shape with existing shape information mergeInShapeInfo(inferred_shape, *mutable_scan_output_tensor_type); } } } } } static const char* scan_opset8_doc = R"DOC( Scan can be used to iterate over one or more scan_input tensors, constructing zero or more scan_output tensors. It combines ideas from general recurrences, functional programming constructs such as scan, fold, map, and zip and is intended to enable generalizations of RNN-like constructs for sequence-to-sequence processing. Other tensors (referred to as state_variables here) can be used to carry a state when iterating from one element to another (similar to hidden-state in RNNs, also referred to as loop-carried dependences in the context of loops). All these tensors are required to have the same shape in each iteration of the loop (a restriction imposed to enable efficient memory allocation). Many common usages involve a single scan_input tensor (where functionality similar to scan, fold and map can be obtained). When more than one scan_input is used, a behavior similar to zip is obtained. The attribute body must be a graph, specifying the computation to be performed in every iteration. It takes as input the current values of the state_variables and the current iterated element of the scan_inputs. It must return the (updated) values of the state_variables and zero or more scan_output_element tensors. The values of the scan_output_element tensors are concatenated over all the iterations to produce the scan_output values of the scan construct (similar to the concatenated intermediate hidden-state values of RNN-like constructs). The scan operation returns the final values of the state_variables as well as the scan_outputs. The operation supports batching, and the batch-axis is required to be 0. When multiple scan_input tensors are used, they must all have the same batch-size, and they must all have the same maximum-sequence-length (the dimensionality of the sequence axis or scan axis). The sequence axis or scan axis is required to be 1. The operation has an optional sequence_lens input (of shape [BATCH_SIZE]) to allow variable length sequences of length <= the maximum-sequence-length. If this input is not specified, all sequences are assumed to be of length equal to maximum-sequence-length. For variable length input sequences, the scan_outputs will consist of a sequence of same length as the input, padded to the maximum-sequence-length. The optional attribute directions can be used to scan a sequence in the reverse direction. If this attribute is omitted, all sequences are scanned in the forward direction. A bidirectional scan be performed by specifying the same tensor input twice in the scan_inputs, once with a forward direction, and once with a backward direction. Note that because of the ONNX restriction that only the last parameter of an operator can be variadic, the initial-states and scan-inputs are listed together as one input parameter. Similarly, the final-states and scan-outputs are listed together as one output parameter. The attribute num_scan_inputs indicates the number M of scan-inputs. The behavior of Scan < num_scan_inputs = m, body = loop-body > (sequence_lengths, init_1, ..., init_n, scan_1, ..., scan_m) is equivalent to the following pseudo-code: // T.shape[0] denotes the batch-size of T // The batch-size of scan_1, ..., scan_m are all required to be equal batch_size = scan_1.shape[0]; // scan_i.shape[1] denotes the (max) sequence-length of scan_i // scan_i.shape[1] is required to be equal to scan_j.shape[1] for all i,j. max_sequence_length = scan_1.shape[1]; for (int batch = 0; batch < batch_size; ++batch) { // initialize state-variables st_1 = init_1; ... st_n = init_n; // initialize scan-output variables: [] denotes an empty tensor scan_out_1 = []; ...; scan_out_k = []; // identify number of iterations: N = (sequence_lengths specified) ? sequence_lengths[batch] : max_sequence_length; // execute loop for (int t = 0; t < N; ++t) { // generate the scan-input elements: the notation T[t] indicates the sub-tensor // of rank one less than T obtained by indexing T at position t along axis k. si_1 = (scan_1[batch])[t]; ... ; si_m = (scan_m[batch])[t]; // execute loop-body st_1, ..., st_n, so_1, ..., so_k = loop-body(st_1, ..., st_n, si_1, ..., si_m) // accumulate the scan-output elements scan_out_1 = Concat(scan_out_1, so_1); ... ; scan_out_k = Concat(scan_out_k, so_k); } // accumulate the outputs for this batch: bst_1[batch] = st_1; ..., bst_n[batch] = st_n; // Note scan-outputs will have size max_sequence_length, but only first N values will be meaningful. // The remaining values have an undefined value. b_scan_out_1[batch] = scan_out_1; ...; b_scan_out_k[batch] = scan_out_k; } return bst_1, ..., bst_n, b_scan_out_1, ..., b_scan_out_k; *Sample usage: Encoding RNN using a Scan* The following example shows how a simple RNN over an input tensor %X, with weight tensor %Wi, recurrence weight tensor %Ri, bias tensors %Wbi and %Rbi, and initial hidden-state %H_0 can be encoded as a ScanLoop. Note that the loop-body is a nested graph, and it directly computes %Wi, %Ri, %Wbi, and %Rbi (typically constants or initializers in the body graph). If these values are computed in the outer graph, they need to be passed in as extra state_variables. graph rnn-encoding { %H_0 = ... %X = ... %Y_h, %Y = Scan[body = , num_scan_inputs=1]("", %H_0, %X) return %Y, %Y_h } graph rnn-cell-1 ( %H_tminus1[FLOAT, tensor] %X_t[FLOAT, tensor] ) { %Wi = ... %Ri = ... %Wbi = ... %Rbi = ... %t1 = X_t * (Wi^T) %t2 = H_tminus1*(Ri^T) %t3 = Add(%t1, %t2) %t4 = Add(%t3, %Wbi) %t5 = Add(%t4, %Rbi) %Ht = Tanh(%t5) %Accumulate = Identity(%Ht) return %Ht, %Accumulate } )DOC"; ONNX_OPERATOR_SET_SCHEMA( Scan, 8, OpSchema() .SetDoc(scan_opset8_doc) .Input( 0, "sequence_lens", "Optional tensor specifying lengths of the sequences in a batch. " "If this input is not specified, all sequences are assumed to be of " "the maximum sequence length (the dimension of the sequence axis of " "the scan_input tensors).", "I", OpSchema::Optional) .Input( 1, "initial_state_and_scan_inputs", "Initial values of the loop's N state variables followed by M scan_inputs", "V", OpSchema::Variadic, false) .Output( 0, "final_state_and_scan_outputs", "Final values of the loop's N state variables followed by K scan_outputs", "V", OpSchema::Variadic, false) .Attr( "body", "The graph run each iteration. It has N+M inputs: " "(loop state variables..., scan_input_elts...). It has N+K outputs: " "(loop state variables..., scan_output_elts...). Each " "scan_output is created by concatenating the value of the specified " "scan_output_elt value at the end of each iteration of the loop. It is an error" " if the dimensions of these values change across loop iterations.", AttributeProto::GRAPH, true) .Attr( "num_scan_inputs", "An attribute specifying the number of scan_inputs M. ", AttributeProto::INT, true) .Attr( "directions", "An optional list of M flags. The i-th element of the list specifies the direction " "to be scanned for the i-th scan_input tensor: 0 indicates forward direction and 1 " "indicates reverse direction. " "If omitted, all scan_input tensors will be scanned in the forward direction.", AttributeProto::INTS, false) .TypeConstraint("I", {"tensor(int64)"}, "Int64 tensor") .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeAndShapeInferenceFunction(ScanInferenceFunctionOpset8)); void LoopInferenceFunctionOpset8(InferenceContext& ctx) { auto num_inputs = ctx.getNumInputs(); auto num_loop_state_vars = num_inputs - 2; // skip 'M' and 'cond' std::vector subgraph_input_types; std::vector temporary_type_protos; temporary_type_protos.reserve(num_inputs - 2); // create TypeProto to validate iteration number type is the same as the // optional 'M' input for max iterations. TypeProto iter_num_type; iter_num_type.mutable_tensor_type()->set_elem_type( TensorProto_DataType_INT64); subgraph_input_types.push_back(&iter_num_type); // 'cond' subgraph_input_types.push_back(ctx.getInputType(1)); // loop state value types get propagated to outputs, but shape may change // across iterations so don't propagate it to the outputs and don't pass it // into the subgraph inferencing for (size_t i = 2; i < num_inputs; ++i) { propagateElemTypeFromInputToOutput(ctx, i, i - 2); // copy so we can remove the shape before passing to the subgraph // inferencing temporary_type_protos.push_back(*ctx.getInputType(i)); auto& input_type = temporary_type_protos.back(); input_type.mutable_tensor_type()->clear_shape(); subgraph_input_types.push_back(&input_type); } // Run inferencing on the subgraph std::vector subgraph_output_types; GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("body"); if (graphInferencer) { std::vector input_data; input_data.push_back(nullptr); // iteration number for (size_t i = 1; i < num_inputs; ++i) { input_data.push_back(ctx.getInputData(i)); } subgraph_output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } // if empty(), assume inferencing was skipped if (!subgraph_output_types.empty()) { auto num_outputs = ctx.getNumOutputs(); // subgraph outputs the condition value first but that is only used // internally and not returned by Loop. if (subgraph_output_types.size() != num_outputs + 1) { fail_type_inference( "Graph attribute inferencing returned type information for ", subgraph_output_types.size(), " outputs. Expected ", num_outputs + 1); } // check loop state values match. we should already have type/shape info for (size_t i = 0; i < num_outputs; ++i) { auto* subgraph_output_type = subgraph_output_types[i + 1]; // skip 'cond' auto* loop_output_type = ctx.getOutputType(i); const bool is_loop_state_var = i < num_loop_state_vars; if (!subgraph_output_type->has_tensor_type()) { fail_type_inference( "Loop 'body' subgraph outputs should all be tensors but output ", i, " was ", subgraph_output_type->value_case()); } // if there's an existing type check it matches. otherwise propagate propagateElemTypeWithValidation(subgraph_output_type, loop_output_type); if (is_loop_state_var) { // shape may change across iterations so ignore. } else { // per iteration output. first dimension will be number of iterations // but we don't know that value yet TypeProto inferred_type(*subgraph_output_type); auto* mutable_inferred_tensor_type = inferred_type.mutable_tensor_type(); auto* mutable_inferred_shape = mutable_inferred_tensor_type->mutable_shape(); mutable_inferred_shape->clear_dim(); // add empty dimension for number of iterations mutable_inferred_shape->add_dim(); // add dimensions from subgraph output shape for (const auto& dim : subgraph_output_type->tensor_type().shape().dim()) { (*mutable_inferred_shape->add_dim()) = dim; } mergeInShapeInfo( *mutable_inferred_tensor_type, *loop_output_type->mutable_tensor_type()); } } } } static const char* Loop_ver1_doc = R"DOC( Generic Looping construct. This loop has multiple termination conditions: 1) Trip count. Iteration count specified at runtime. Set by specifying the input M. Optional. Set to empty string to omit. Note that a static trip count (specified at graph construction time) can be specified by passing in a constant node for input M. 2) Loop termination condition. This is an input to the op that determines whether to run the first iteration and also a loop-carried dependency for the body graph. The body graph must yield a value for the condition variable, whether this input is provided or not. This table summarizes the operating modes of this operator with equivalent C-style code: Operator inputs defined as (max_trip_count, condition_var). input ("", ""): for (int i=0; ; ++i) { cond = ... // Note this value is ignored, but is required in the body } input ("", cond) // Note this is analogous to a while loop bool cond = ...; for (int i=0; cond; ++i) { cond = ...; } input ("", 1) // Note this is analogous to a do-while loop bool cond = true for (int i=0; cond; ++i) { cond = ...; } input (trip_count, "") // Note this is analogous to a for loop int trip_count = ... for (int i=0; i < trip_count; ++i) { cond = ...; // ignored } input (trip_count, cond) int trip_count = ...; bool cond = ...; for (int i=0; i < trip_count && cond; ++i) { cond = ...; } *Sample usage - cond as well as trip count* graph predict-net { %a = Constant[value = ]() %b = Constant[value = ]() %keepgoing = Constant[value = ]() %max_trip_count = Constant[value = ]() %keepgoing_out, %b_out, %user_defined_vals = Loop[body = ](%max_trip_count, %keepgoing, %b) return } graph body-net ( %i[INT32, scalar] %keepgoing[BOOL, scalar] %b[INT32, scalar] ) { %my_local = Add(%a, %b) %b_out = Sub(%a, %b) %keepgoing_out = Greater(%my_local, %b_out) %user_defined_vals = Add(%b, %b) return %keepgoing_out, %b_out, %user_defined_vals } *Sample equivalent C code* { /* User-defined code (enclosing scope) */ int a = 3, b = 6; bool keepgoing = true; // Analogous to input cond /* End user-defined code */ /* Implicitly-defined code */ const int max_trip_count = 10; // Analogous to input M int user_defined_vals[]; // Imagine this is resizable /* End implicitly-defined code */ for (int i=0; i < max_trip_count && keepgoing; ++i) { /* User-defined code (loop body) */ int my_local = a + b; // Reading values in the enclosing scope is fine b = a - b; // writes fine if we specify b as a loop-carried dependency keepgoing = my_local > b; // keepgoing is a loop-carried dependency user_defined_vals[i] = b + b; /* End user-defined code */ } // my_local = 123; // Can't do this. my_local was defined in the the body // These below values are live-out from the loop and therefore accessible b_out; user_defined_vals; keepgoing_out; } There are several things of note in this code snippet: 1) Values from the enclosing scope (i.e. variable a here) are in scope and can be referenced in the inputs of the loop. 2) Any variables which you wish to make available in the enclosing scope (i.e. the variables b and keepgoing) must be declared as either loop-carried dependencies (both at the op inputs and output and at the body net input and output) or scan_outputs. 3) Values created in the body cannot be accessed in the enclosing scope. Note that the semantics of this op support "diagonal" or "wavefront" execution. (See Step 3 here for an example: https://devblogs.nvidia.com/optimizing-recurrent-neural-networks-cudnn-5/). Frontends should emit multi-layer RNNs as a series of While operators (with time being the inner looping dimension), with each successive layer consuming the scan_outputs from the previous layer, possibly going through several point-wise operators (e.g. dropout, residual connections, linear layer). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Loop, 1, OpSchema() .SetDoc(Loop_ver1_doc) .Input( 0, "M", "A maximum trip-count for the loop specified at runtime. Optional." " Pass empty string to skip.", "I", OpSchema::Optional) .Input( 1, "cond", "A boolean termination condition. Optional. Pass empty string to skip.", "B", OpSchema::Optional) .Input( 2, "v_initial", "The initial values of any loop-carried dependencies (values that " "change across loop iterations)", "V", OpSchema::Variadic, false) .Output( 0, "v_final_and_scan_outputs", "Final N loop carried dependency values then K scan_outputs", "V", OpSchema::Variadic, false) .Attr( "body", "The graph run each iteration. It has 2+N inputs: (iteration_num, " "condition, loop carried dependencies...). It has 1+N+K outputs: " "(condition, loop carried dependencies..., scan_outputs...). Each " "scan_output is created by concatenating the value of the specified " "output value at the end of each iteration of the loop. It is an error" " if the dimensions or data type of these scan_outputs change across loop" " iterations.", AttributeProto::GRAPH) .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeConstraint( "I", {"tensor(int64)"}, "tensor of int64, which should be a scalar.") .TypeConstraint( "B", {"tensor(bool)"}, "tensor of bool, which should be a scalar.") .TypeAndShapeInferenceFunction(LoopInferenceFunctionOpset8)); static const char* scan_9_doc = R"DOC( Scan can be used to iterate over one or more scan_input tensors, constructing zero or more scan_output tensors. It combines ideas from general recurrences, functional programming constructs such as scan, fold, map, and zip and is intended to enable generalizations of RNN-like constructs for sequence-to-sequence processing. Other tensors (referred to as state_variables here) can be used to carry a state when iterating from one element to another (similar to hidden-state in RNNs, also referred to as loop-carried dependences in the context of loops). Many common usages involve a single scan_input tensor (where functionality similar to scan, fold and map can be obtained). When more than one scan_input is used, a behavior similar to zip is obtained. The attribute body must be a graph, specifying the computation to be performed in every iteration. It takes as input the current values of the state_variables and the current iterated element of the scan_inputs. It must return the (updated) values of the state_variables and zero or more scan_output_element tensors. The values of the scan_output_element tensors are concatenated over all the iterations to produce the scan_output values of the scan construct (similar to the concatenated intermediate hidden-state values of RNN-like constructs). All the output tensors (state_variables as well as scan_output_element tensors) are required to have the same shape in each iteration of the loop (a restriction imposed to enable efficient memory allocation). Note that the iterated element passed to the body subgraph does not have a sequence axis. It will have a rank one less than the rank of the corresponding scan_input. The scan operation returns the final values of the state_variables as well as the scan_outputs. The optional attribute scan_input_directions specifies the direction (forward or backward) for each scan input. If this attribute is omitted, all sequences are scanned in the forward direction. A bidirectional scan may be performed by specifying the same tensor input twice in the scan_inputs, once with a forward direction, and once with a backward direction. The scan_output of the operation is produced by concatenating the scan_output_element values produced by the body in each iteration. The optional attribute scan_output_directions specifies the direction in which scan_output is constructed (by appending or prepending the scan_output_element to scan_output in each iteration) for each scan_output. If this attribute is omitted, the scan_output_element is appended to the scan_output in each iteration. The optional attribute scan_input_axes specifies the axis to be scanned for each scan_input. If omitted, every scan_input will be scanned in axis 0. For example, if axis 0 is the batch axis and axis 1 is the time axis (to be scanned), specify an axis value of 1. Note that scanning a non-zero axis may be less efficient than scanning axis zero. The optional attribute scan_output_axes specifies the axis along which the scan_outputs are accumulated for each scan_output. For example, if axis 1 is the time axis (to be scanned) for both inputs and outputs, specify a scan_input axis and scan_output axis value of 1. Note that because of the ONNX restriction that only the last parameter of an operator can be variadic, the initial-states and scan-inputs are listed together as one input parameter. Similarly, the final-states and scan-outputs are listed together as one output parameter. The attribute num_scan_inputs indicates the number M of scan-inputs. The behavior of Scan < num_scan_inputs = m, body = loop-body, scan_input_axes = [axis_1, ..., axis_m] > (init_1, ..., init_n, scan_1, ..., scan_m) is equivalent to the following pseudo-code: // scan_i.shape[axis_i] denotes the (max) sequence-length of scan_i // scan_i.shape[axis_i] is required to be equal to scan_j.shape[axis_j] for all i,j. sequence_length = scan_1.shape[axis_1]; // initialize state-variables st_1 = init_1; ... st_n = init_n; // initialize scan-output variables: [] denotes an empty tensor scan_out_1 = []; ...; scan_out_k = []; // identify number of iterations: // execute loop for (int t = 0; t < sequence_length; ++t) { // generate the scan-input elements: the notation T[t] indicates the sub-tensor // of rank one less than T obtained by indexing T at position t along axis k. si_1 = scan_1[t]; ... ; si_m = scan_m[t]; // execute loop-body st_1, ..., st_n, so_1, ..., so_k = loop-body(st_1, ..., st_n, si_1, ..., si_m) // accumulate the scan-output elements scan_out_1 = Concat(scan_out_1, so_1); ... ; scan_out_k = Concat(scan_out_k, so_k); } return st_1, ..., st_n, scan_out_1, ..., scan_out_k; *Sample usage: Encoding RNN using a Scan* The following example shows how a simple RNN over an input tensor %X, with weight tensor %Wi, recurrence weight tensor %Ri, bias tensors %Wbi and %Rbi, and initial hidden-state %H_0 can be encoded as a ScanLoop. Note that the loop-body is a nested graph, and it directly computes %Wi, %Ri, %Wbi, and %Rbi (typically constants or initializers in the body graph). If these values are computed in the outer graph, they need to be passed in as extra state_variables. graph rnn-encoding { %H_0 = ... %X = ... %Y_h, %Y = Scan[body = , num_scan_inputs=1](%H_0, %X) return %Y, %Y_h } graph rnn-cell-1 ( %H_tminus1[FLOAT, tensor] %X_t[FLOAT, tensor] ) { %Wi = ... %Ri = ... %Wbi = ... %Rbi = ... %t1 = X_t * (Wi^T) %t2 = H_tminus1*(Ri^T) %t3 = Add(%t1, %t2) %t4 = Add(%t3, %Wbi) %t5 = Add(%t4, %Rbi) %Ht = Tanh(%t5) %Accumulate = Identity(%Ht) return %Ht, %Accumulate } )DOC"; ONNX_OPERATOR_SET_SCHEMA( Scan, 9, OpSchema() .SetDoc(scan_9_doc) .Input( 0, "initial_state_and_scan_inputs", "Initial values of the loop's N state variables followed by M scan_inputs", "V", OpSchema::Variadic, false) .Output( 0, "final_state_and_scan_outputs", "Final values of the loop's N state variables followed by K scan_outputs", "V", OpSchema::Variadic, false) .Attr( "body", "The graph run each iteration. It has N+M inputs: " "(loop state variables..., scan_input_elts...). It has N+K outputs: " "(loop state variables..., scan_output_elts...). Each " "scan_output is created by concatenating the value of the specified " "scan_output_elt value at the end of each iteration of the loop. It is an error" " if the dimensions of these values change across loop iterations.", AttributeProto::GRAPH, true) .Attr( "num_scan_inputs", "An attribute specifying the number of scan_inputs M. ", AttributeProto::INT, true) .Attr( "scan_input_directions", "An optional list of M flags. The i-th element of the list specifies the direction " "to be scanned for the i-th scan_input tensor: 0 indicates forward direction and 1 " "indicates reverse direction. " "If omitted, all scan_input tensors will be scanned in the forward direction.", AttributeProto::INTS, false) .Attr( "scan_output_directions", "An optional list of K flags, one for each scan_output. The i-th element of the list " "specifies whether the i-th scan_output should be constructed by appending or " "prepending a new value in each iteration: 0 indicates appending and 1 " "indicates prepending. " "If omitted, all scan_output tensors will be produced by appending a value " "in each iteration.", AttributeProto::INTS, false) .Attr( "scan_input_axes", "An optional list of M flags. The i-th element of the list specifies the axis " "to be scanned (the sequence axis) for the i-th scan_input. If omitted, 0 will " "be used as the scan axis for every scan_input.", AttributeProto::INTS, false) .Attr( "scan_output_axes", "An optional list of K flags. The i-th element of the list specifies the axis " "for the i-th scan_output. The scan outputs are accumulated along the specified " "axis. If omitted, 0 will be used as the scan axis for every scan_output.", AttributeProto::INTS, false) .TypeConstraint("I", {"tensor(int64)"}, "Int64 tensor") .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeAndShapeInferenceFunction(ScanInferenceFunctionOpset9)); void IfInferenceFunction1(InferenceContext& ctx) { // there are no inputs so we just need to run the subgraph inferencing for // then/else subgraphs and apply those to the outputs. std::vector subgraph_input_types; // none std::vector input_data; // none std::vector then_output_types; std::vector else_output_types; // Run inferencing on the subgraph GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("then_branch"); if (graphInferencer) { then_output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } graphInferencer = ctx.getGraphAttributeInferencer("else_branch"); if (graphInferencer) { else_output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } auto num_outputs = ctx.getNumOutputs(); auto num_then_outputs = then_output_types.size(); auto num_else_outputs = else_output_types.size(); // the output types for then and else should be the same if (num_then_outputs != num_else_outputs) { fail_type_inference( "then_branch and else_branch produce different number of outputs. ", num_then_outputs, " != ", num_else_outputs); } if (num_then_outputs != num_outputs) { fail_type_inference( "If node has ", num_outputs, " but subgraphs produce ", num_then_outputs); } for (size_t i = 0, end = then_output_types.size(); i < end; ++i) { auto then_output = then_output_types[i]; auto else_output = else_output_types[i]; if (then_output->value_case() != else_output->value_case()) { fail_type_inference( "Mismatched type for output ", i, " then=", then_output->value_case(), " else=", else_output->value_case()); } auto* if_output = ctx.getOutputType(i); *if_output = *then_output; if (then_output->has_tensor_type()) { auto then_elem_type = then_output->tensor_type().elem_type(); auto else_elem_type = else_output->tensor_type().elem_type(); if (then_elem_type != else_elem_type) { fail_type_inference( "Mismatched tensor element type for output ", i, " then=", then_elem_type, " else=", else_elem_type); } // merge the 'else' shape information to check it's consistent and // augment the 'if' output if possible mergeInShapeInfo( else_output->tensor_type(), *if_output->mutable_tensor_type()); } } } ONNX_OPERATOR_SET_SCHEMA( If, 1, OpSchema() .SetDoc("If conditional") .Input(0, "cond", "Condition for the if", "B") .Output( 0, "outputs", "Values that are live-out to the enclosing scope. The return values in " "the `then_branch` and `else_branch` must be of the same shape and same " "data type.", "V", OpSchema::Variadic, false) .Attr( "then_branch", "Graph to run if condition is true. Has N outputs: values you wish to " "be live-out to the enclosing scope. The number of outputs must match" " the number of outputs in the else_branch.", AttributeProto::GRAPH) .Attr( "else_branch", "Graph to run if condition is false. Has N outputs: values you wish to" " be live-out to the enclosing scope. The number of outputs must match" " the number of outputs in the then_branch.", AttributeProto::GRAPH) .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeConstraint("B", {"tensor(bool)"}, "Only bool") .TypeAndShapeInferenceFunction(IfInferenceFunction1)); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/controlflow/defs.cc0000664000000000000000000007773113655345213016732 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/schema.h" namespace ONNX_NAMESPACE { using SupportType = OpSchema::SupportType; int handle_negative_axis_validate( const std::string& attrib, int axis, int rank) { if (!(-rank <= axis && axis < rank)) fail_shape_inference( attrib, " axis value ", axis, " is invalid for a tensor of rank ", rank); return (axis >= 0 ? axis : axis + rank); } void ScanInferenceFunction(InferenceContext& ctx) { auto num_inputs = ctx.getNumInputs(); auto num_scan_inputs = narrow_cast(ctx.getAttribute("num_scan_inputs")->i()); auto num_loop_state_vars = num_inputs - num_scan_inputs; auto num_outputs = ctx.getNumOutputs(); auto num_scan_outputs = num_outputs - num_loop_state_vars; std::vector axes, output_axes; if (getRepeatedAttribute(ctx, "scan_input_axes", axes)) { if (axes.size() != num_scan_inputs) fail_shape_inference( "Number of scan input axes specified (", axes.size(), ") is not equal to number of scan inputs (", num_scan_inputs, ")."); } else { axes.insert(axes.end(), num_scan_inputs, 0); } if (getRepeatedAttribute(ctx, "scan_output_axes", output_axes)) { if (output_axes.size() != num_scan_outputs) fail_shape_inference( "Number of scan output axes specified (", output_axes.size(), ") is not equal to number of scan outputs (", num_scan_outputs, ")."); } else { output_axes.insert(output_axes.end(), num_scan_outputs, 0); } std::vector temporary_type_protos; temporary_type_protos.reserve(num_inputs); std::vector subgraph_input_types; TensorShapeProto_Dimension sequence_len_dim; for (size_t i = 0; i < num_inputs; ++i) { bool is_loop_state_var = i < num_loop_state_vars; bool has_shape = hasInputShape(ctx, i); const auto* input_type = ctx.getInputType(i); // Enforce type constraint for inputs if (!input_type || !input_type->has_tensor_type()) { fail_type_inference("Scan input ", i, " was not a tensor."); } if (is_loop_state_var) { // If it's a loop state variable we can propagate type and shape 1:1 to // the matching Scan output. // We can also pass through the type and shape to the subgraph but need to // remove the batch size dimension from the shape. propagateElemTypeFromInputToOutput(ctx, i, i); if (has_shape) propagateShapeFromInputToOutput(ctx, i, i); subgraph_input_types.push_back(input_type); } else { // For other inputs there is no fixed relationships to the Scan outputs, // so we don't propagate type/shape information. // We can pass through the type and shape to the subgraph inputs but // need to remove the sequence length dimensions from the shape. if (has_shape) { const auto& shape = input_type->tensor_type().shape(); // remove sequence length dimensions and add to subgraph_input_types int axis = static_cast(axes[i - num_loop_state_vars]); axis = handle_negative_axis_validate( "scan_input_axes", axis, shape.dim_size()); // update sequence_len if a value is available const auto& dims = shape.dim(); mergeInDimensionInfo(dims.Get(axis), sequence_len_dim, 1); temporary_type_protos.push_back( RemoveIthDimensionFromShape(*input_type, axis)); subgraph_input_types.push_back(&temporary_type_protos.back()); } else { subgraph_input_types.push_back(input_type); } } } // Run inferencing on the subgraph std::vector output_types; GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("body"); if (graphInferencer) { std::vector input_data; for (size_t i = 0; i < num_inputs; ++i) { // ctx.getInputData(i), the input to scan, does not represent the input to // scan body. So, we pass in null, to represent an unknown value. input_data.push_back(nullptr); } output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } // if empty(), assume inferencing was skipped if (!output_types.empty()) { if (output_types.size() != num_outputs) { fail_type_inference( "Graph attribute inferencing returned type information for ", output_types.size(), " outputs. Expected ", num_outputs); } // propagate type/shape information for loop state variables and outputs for (size_t i = 0; i < num_outputs; ++i) { const bool is_loop_state_var = i < num_loop_state_vars; auto* subgraph_output_type = output_types[i]; auto* scan_output_type = ctx.getOutputType(i); auto* mutable_scan_output_tensor_type = scan_output_type->mutable_tensor_type(); if (!subgraph_output_type->has_tensor_type()) { fail_type_inference( "Scan 'body' subgraph outputs should all be tensors but output ", i, " was not"); } auto& subgraph_output_tensor_type = subgraph_output_type->tensor_type(); if (is_loop_state_var) { // merge shape; type already propagated mergeInShapeInfo( subgraph_output_tensor_type, *mutable_scan_output_tensor_type); } else { scan_output_type->mutable_tensor_type()->set_elem_type( subgraph_output_tensor_type.elem_type()); // propagate shape if (subgraph_output_tensor_type.has_shape()) { // infer shape of scan-output from the shape of scan-output-element // by adding sequence-length at the correct axis position const TensorShapeProto& subgraph_output_shape = subgraph_output_tensor_type.shape(); TensorShapeProto inferred_shape; auto subgraph_output_rank = subgraph_output_shape.dim_size(); auto output_rank = subgraph_output_rank + 1; int output_axis = static_cast(output_axes[i - num_loop_state_vars]); output_axis = handle_negative_axis_validate( "scan_output_axes", output_axis, output_rank); for (int j = 0; j < output_axis; ++j) *(inferred_shape.add_dim()) = subgraph_output_shape.dim(j); *(inferred_shape.add_dim()) = sequence_len_dim; for (int j = output_axis; j < subgraph_output_rank; ++j) *(inferred_shape.add_dim()) = subgraph_output_shape.dim(j); // Merge inferred shape with existing shape information mergeInShapeInfo(inferred_shape, *mutable_scan_output_tensor_type); } } } } } void IfInferenceFunction(InferenceContext& ctx) { // there are no inputs so we just need to run the subgraph inferencing for // then/else subgraphs and apply those to the outputs. std::vector subgraph_input_types; // none std::vector input_data; // none std::vector then_output_types; std::vector else_output_types; // Run inferencing on the subgraph GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("then_branch"); if (graphInferencer) { then_output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } graphInferencer = ctx.getGraphAttributeInferencer("else_branch"); if (graphInferencer) { else_output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } auto num_outputs = ctx.getNumOutputs(); auto num_then_outputs = then_output_types.size(); auto num_else_outputs = else_output_types.size(); // the output types for then and else should be the same if (num_then_outputs != num_else_outputs) { fail_type_inference( "then_branch and else_branch produce different number of outputs. ", num_then_outputs, " != ", num_else_outputs); } if (num_then_outputs != num_outputs) { fail_type_inference( "If node has ", num_outputs, " but subgraphs produce ", num_then_outputs); } for (size_t i = 0, end = then_output_types.size(); i < end; ++i) { auto then_output = then_output_types[i]; auto else_output = else_output_types[i]; if (then_output->value_case() != else_output->value_case()) { fail_type_inference( "Mismatched type for output ", i, " then=", then_output->value_case(), " else=", else_output->value_case()); } auto* if_output = ctx.getOutputType(i); *if_output = *then_output; if (then_output->has_tensor_type()) { auto then_elem_type = then_output->tensor_type().elem_type(); auto else_elem_type = else_output->tensor_type().elem_type(); if (then_elem_type != else_elem_type) { fail_type_inference( "Mismatched tensor element type for output ", i, " then=", then_elem_type, " else=", else_elem_type); } UnionShapeInfo( else_output->tensor_type().shape(), *if_output->mutable_tensor_type()); } } } void LoopInferenceFunction(InferenceContext& ctx) { auto num_inputs = ctx.getNumInputs(); auto num_loop_state_vars = num_inputs - 2; // skip 'M' and 'cond' std::vector subgraph_input_types; std::vector temporary_type_protos; temporary_type_protos.reserve(num_inputs - 2); // create TypeProto to validate iteration number type is the same as the // optional 'M' input for max iterations. TypeProto iter_num_type; iter_num_type.mutable_tensor_type()->set_elem_type( TensorProto_DataType_INT64); subgraph_input_types.push_back(&iter_num_type); // 'cond' subgraph_input_types.push_back(ctx.getInputType(1)); // loop state value types get propagated to outputs, but shape may change // across iterations so don't propagate it to the outputs and don't pass it // into the subgraph inferencing for (size_t i = 2; i < num_inputs; ++i) { propagateElemTypeFromInputToOutput(ctx, i, i - 2); // copy so we can remove the shape before passing to the subgraph // inferencing temporary_type_protos.push_back(*ctx.getInputType(i)); auto& input_type = temporary_type_protos.back(); input_type.mutable_tensor_type()->clear_shape(); subgraph_input_types.push_back(&input_type); } // Run inferencing on the subgraph std::vector subgraph_output_types; GraphInferencer* graphInferencer = ctx.getGraphAttributeInferencer("body"); if (graphInferencer) { std::vector input_data; input_data.push_back(nullptr); // iteration number for (size_t i = 1; i < num_inputs; ++i) { input_data.push_back(ctx.getInputData(i)); } subgraph_output_types = graphInferencer->doInferencing(subgraph_input_types, input_data); } // if empty(), assume inferencing was skipped if (!subgraph_output_types.empty()) { auto num_outputs = ctx.getNumOutputs(); // subgraph outputs the condition value first but that is only used // internally and not returned by Loop. if (subgraph_output_types.size() != num_outputs + 1) { fail_type_inference( "Graph attribute inferencing returned type information for ", subgraph_output_types.size(), " outputs. Expected ", num_outputs + 1); } // check loop state values match. we should already have type/shape info for (size_t i = 0; i < num_outputs; ++i) { auto* subgraph_output_type = subgraph_output_types[i + 1]; // skip 'cond' auto* loop_output_type = ctx.getOutputType(i); const bool is_loop_state_var = i < num_loop_state_vars; if (!subgraph_output_type->has_tensor_type()) { fail_type_inference( "Loop 'body' subgraph outputs should all be tensors but output ", i, " was ", subgraph_output_type->value_case()); } // if there's an existing type check it matches. otherwise propagate propagateElemTypeWithValidation(subgraph_output_type, loop_output_type); if (is_loop_state_var) { // shape may change across iterations so ignore. } else { // per iteration output. first dimension will be number of iterations // but we don't know that value yet TypeProto inferred_type(*subgraph_output_type); auto* mutable_inferred_tensor_type = inferred_type.mutable_tensor_type(); auto* mutable_inferred_shape = mutable_inferred_tensor_type->mutable_shape(); mutable_inferred_shape->clear_dim(); // add empty dimension for number of iterations mutable_inferred_shape->add_dim(); // add dimensions from subgraph output shape for (const auto& dim : subgraph_output_type->tensor_type().shape().dim()) { (*mutable_inferred_shape->add_dim()) = dim; } mergeInShapeInfo( *mutable_inferred_tensor_type, *loop_output_type->mutable_tensor_type()); } } } } ONNX_OPERATOR_SET_SCHEMA( If, 11, OpSchema() .SetDoc("If conditional") .Input(0, "cond", "Condition for the if", "B") .Output( 0, "outputs", "Values that are live-out to the enclosing scope. The return values in " "the `then_branch` and `else_branch` must be of the same data type. " "The `then_branch` and `else_branch` may produce tensors with the same " "element type and different shapes. " "If corresponding outputs from the then-branch and the else-branch have " "static shapes S1 and S2, then the shape of the corresponding output " "variable of the if-node (if present) must be compatible with both S1 " "and S2 as it represents the union of both possible shapes." "For example, if in a model file, the the first " "output of `then_branch` is typed float tensor with shape [2] and the " "first output of `else_branch` is another float tensor with shape [3], " "If's first output should have (a) no shape set, or (b) " "a shape of rank 1 with neither `dim_value` nor `dim_param` set, or (c) " "a shape of rank 1 with a unique `dim_param`. " "In contrast, the first output cannot have the shape [2] since [2] and " "[3] are not compatible.", "V", OpSchema::Variadic, false) .Attr( "then_branch", "Graph to run if condition is true. Has N outputs: values you wish to " "be live-out to the enclosing scope. The number of outputs must match" " the number of outputs in the else_branch.", AttributeProto::GRAPH) .Attr( "else_branch", "Graph to run if condition is false. Has N outputs: values you wish to" " be live-out to the enclosing scope. The number of outputs must match" " the number of outputs in the then_branch.", AttributeProto::GRAPH) .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeConstraint("B", {"tensor(bool)"}, "Only bool") .TypeAndShapeInferenceFunction(IfInferenceFunction)); static const char* Loop_ver11_doc = R"DOC( Generic Looping construct. This loop has multiple termination conditions: 1) Trip count. Iteration count specified at runtime. Set by specifying the input M. Optional. Set to empty string to omit. Note that a static trip count (specified at graph construction time) can be specified by passing in a constant node for input M. 2) Loop termination condition. This is an input to the op that determines whether to run the first iteration and also a loop-carried dependency for the body graph. The body graph must yield a value for the condition variable, whether this input is provided or not. This table summarizes the operating modes of this operator with equivalent C-style code: Operator inputs defined as (max_trip_count, condition_var). input ("", ""): for (int i=0; ; ++i) { cond = ... // Note this value is ignored, but is required in the body } input ("", cond) // Note this is analogous to a while loop bool cond = ...; for (int i=0; cond; ++i) { cond = ...; } input ("", 1) // Note this is analogous to a do-while loop bool cond = true for (int i=0; cond; ++i) { cond = ...; } input (trip_count, "") // Note this is analogous to a for loop int trip_count = ... for (int i=0; i < trip_count; ++i) { cond = ...; // ignored } input (trip_count, cond) int trip_count = ...; bool cond = ...; for (int i=0; i < trip_count && cond; ++i) { cond = ...; } *Sample usage - cond as well as trip count* graph predict-net { %a = Constant[value = ]() %b = Constant[value = ]() %keepgoing = Constant[value = ]() %max_trip_count = Constant[value = ]() %keepgoing_out, %b_out, %user_defined_vals = Loop[body = ](%max_trip_count, %keepgoing, %b) return } graph body-net ( %i[INT32, scalar] // iteration number %keepgoing_in[BOOL, scalar] // incoming loop-termination-condition; not used %b_in[INT32, scalar] // incoming value of loop-carried-dependency b ) { %my_local = Add(%a, %b_in) %b_out = Sub(%a, %b_in) // outgoing value of loop-carried-dependency b %keepgoing_out = Greater(%my_local, %b_out) // outgoing loop-termination-condition %user_defined_val = Add(%b_in, %b_in) // scan-output value to be accumulated return %keepgoing_out, %b_out, %user_defined_val } *Sample equivalent C code* { /* User-defined code (enclosing scope) */ int a = 3, b = 6; bool keepgoing = true; // Analogous to input cond /* End user-defined code */ /* Implicitly-defined code */ const int max_trip_count = 10; // Analogous to input M int user_defined_vals[]; // Imagine this is resizable /* End implicitly-defined code */ /* initialize loop-carried variables and scan-output variables */ bool keepgoing_out = keepgoing int b_out = b for (int i=0; i < max_trip_count && keepgoing_out; ++i) { /* Implicitly-defined code: bind actual parameter values to formal parameter variables of loop-body */ bool keepgoing_in = keepgoing_out; bool b_in = b_out; /* User-defined code (loop body) */ int my_local = a + b_in; // Reading value "a" from the enclosing scope is fine b_out = a - b_in; keepgoing_out = my_local > b_out; user_defined_val = b_in + b_in; // b_in and b_out are different variables /* End user-defined code */ /* Implicitly defined-code */ user_defined_vals[i] = user_defined_val // accumulate scan-output values } // int t = my_local; // Can't do this. my_local is not accessible here. // The values below are bound to the output variables of the loop and therefore accessible // b_out; user_defined_vals; keepgoing_out; } There are several things of note in this code snippet: 1) Values from the enclosing scope (i.e. variable "a" here) are in scope and can be referenced in the inputs of the loop. 2) Any values computed in the loop body that needs to be used in a subsequent iteration or after the loop are modelled using a pair of variables in the loop-body, consisting of an input variable (eg., b_in) and an output variable (eg., b_out). These are referred to as loop-carried dependences. The loop operation node supplies the input value of the input variable for the first iteration, and returns the output value of the output variable produced by the final iteration. 3) Scan_output variables are used to implicitly concatenate values computed across all the iterations. In the above example, the value of user_defined_val computed over all iterations are concatenated and returned as the value of user_defined_vals after the loop. 4) Values created in the body cannot be accessed in the enclosing scope, except using the mechanism described above. Note that the semantics of this op support "diagonal" or "wavefront" execution. (See Step 3 here for an example: https://devblogs.nvidia.com/optimizing-recurrent-neural-networks-cudnn-5/). Frontends should emit multi-layer RNNs as a series of While operators (with time being the inner looping dimension), with each successive layer consuming the scan_outputs from the previous layer, possibly going through several point-wise operators (e.g. dropout, residual connections, linear layer). )DOC"; ONNX_OPERATOR_SET_SCHEMA( Loop, 11, OpSchema() .SetDoc(Loop_ver11_doc) .Input( 0, "M", "A maximum trip-count for the loop specified at runtime. Optional." " Pass empty string to skip.", "I", OpSchema::Optional) .Input( 1, "cond", "A boolean termination condition. Optional. Pass empty string to skip.", "B", OpSchema::Optional) .Input( 2, "v_initial", "The initial values of any loop-carried dependencies (values that " "change across loop iterations)", "V", OpSchema::Variadic, false, 0) .Output( 0, "v_final_and_scan_outputs", "Final N loop carried dependency values then K scan_outputs", "V", OpSchema::Variadic, false) .Attr( "body", "The graph run each iteration. It has 2+N inputs: (iteration_num, " "condition, loop carried dependencies...). It has 1+N+K outputs: " "(condition, loop carried dependencies..., scan_outputs...). Each " "scan_output is created by concatenating the value of the specified " "output value at the end of each iteration of the loop. It is an error" " if the dimensions or data type of these scan_outputs change across loop" " iterations.", AttributeProto::GRAPH) .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeConstraint( "I", {"tensor(int64)"}, "tensor of int64, which should be a scalar.") .TypeConstraint( "B", {"tensor(bool)"}, "tensor of bool, which should be a scalar.") .TypeAndShapeInferenceFunction(LoopInferenceFunction)); static const char* scan_11_doc = R"DOC( Scan can be used to iterate over one or more scan_input tensors, constructing zero or more scan_output tensors. It combines ideas from general recurrences, functional programming constructs such as scan, fold, map, and zip and is intended to enable generalizations of RNN-like constructs for sequence-to-sequence processing. Other tensors (referred to as state_variables here) can be used to carry a state when iterating from one element to another (similar to hidden-state in RNNs, also referred to as loop-carried dependences in the context of loops). Many common usages involve a single scan_input tensor (where functionality similar to scan, fold and map can be obtained). When more than one scan_input is used, a behavior similar to zip is obtained. The attribute body must be a graph, specifying the computation to be performed in every iteration. It takes as input the current values of the state_variables and the current iterated element of the scan_inputs. It must return the (updated) values of the state_variables and zero or more scan_output_element tensors. The values of the scan_output_element tensors are concatenated over all the iterations to produce the scan_output values of the scan construct (similar to the concatenated intermediate hidden-state values of RNN-like constructs). All the output tensors (state_variables as well as scan_output_element tensors) are required to have the same shape in each iteration of the loop (a restriction imposed to enable efficient memory allocation). Note that the iterated element passed to the body subgraph does not have a sequence axis. It will have a rank one less than the rank of the corresponding scan_input. The scan operation returns the final values of the state_variables as well as the scan_outputs. The optional attribute scan_input_directions specifies the direction (forward or backward) for each scan input. If this attribute is omitted, all sequences are scanned in the forward direction. A bidirectional scan may be performed by specifying the same tensor input twice in the scan_inputs, once with a forward direction, and once with a backward direction. The scan_output of the operation is produced by concatenating the scan_output_element values produced by the body in each iteration. The optional attribute scan_output_directions specifies the direction in which scan_output is constructed (by appending or prepending the scan_output_element to scan_output in each iteration) for each scan_output. If this attribute is omitted, the scan_output_element is appended to the scan_output in each iteration. The optional attribute scan_input_axes specifies the axis to be scanned for each scan_input. If omitted, every scan_input will be scanned in axis 0. For example, if axis 0 is the batch axis and axis 1 is the time axis (to be scanned), specify an axis value of 1. Note that scanning a non-zero axis may be less efficient than scanning axis zero. The optional attribute scan_output_axes specifies the axis along which the scan_outputs are accumulated for each scan_output. For example, if axis 1 is the time axis (to be scanned) for both inputs and outputs, specify a scan_input axis and scan_output axis value of 1. Note that because of the ONNX restriction that only the last parameter of an operator can be variadic, the initial-states and scan-inputs are listed together as one input parameter. Similarly, the final-states and scan-outputs are listed together as one output parameter. The attribute num_scan_inputs indicates the number M of scan-inputs. The behavior of Scan < num_scan_inputs = m, body = loop-body, scan_input_axes = [axis_1, ..., axis_m] > (init_1, ..., init_n, scan_1, ..., scan_m) is equivalent to the following pseudo-code: // scan_i.shape[axis_i] denotes the (max) sequence-length of scan_i // scan_i.shape[axis_i] is required to be equal to scan_j.shape[axis_j] for all i,j. sequence_length = scan_1.shape[axis_1]; // initialize state-variables st_1 = init_1; ... st_n = init_n; // initialize scan-output variables: [] denotes an empty tensor scan_out_1 = []; ...; scan_out_k = []; // identify number of iterations: // execute loop for (int t = 0; t < sequence_length; ++t) { // generate the scan-input elements: the notation T[t] indicates the sub-tensor // of rank one less than T obtained by indexing T at position t along axis k. si_1 = scan_1[t]; ... ; si_m = scan_m[t]; // execute loop-body st_1, ..., st_n, so_1, ..., so_k = loop-body(st_1, ..., st_n, si_1, ..., si_m) // accumulate the scan-output elements scan_out_1 = Concat(scan_out_1, so_1); ... ; scan_out_k = Concat(scan_out_k, so_k); } return st_1, ..., st_n, scan_out_1, ..., scan_out_k; *Sample usage: Encoding RNN using a Scan* The following example shows how a simple RNN over an input tensor %X, with weight tensor %Wi, recurrence weight tensor %Ri, bias tensors %Wbi and %Rbi, and initial hidden-state %H_0 can be encoded as a ScanLoop. Note that the loop-body is a nested graph, and it directly computes %Wi, %Ri, %Wbi, and %Rbi (typically constants or initializers in the body graph). If these values are computed in the outer graph, they need to be passed in as extra state_variables. graph rnn-encoding { %H_0 = ... %X = ... %Y_h, %Y = Scan[body = , num_scan_inputs=1](%H_0, %X) return %Y, %Y_h } graph rnn-cell-1 ( %H_tminus1[FLOAT, tensor] %X_t[FLOAT, tensor] ) { %Wi = ... %Ri = ... %Wbi = ... %Rbi = ... %t1 = X_t * (Wi^T) %t2 = H_tminus1*(Ri^T) %t3 = Add(%t1, %t2) %t4 = Add(%t3, %Wbi) %t5 = Add(%t4, %Rbi) %Ht = Tanh(%t5) %Accumulate = Identity(%Ht) return %Ht, %Accumulate } )DOC"; ONNX_OPERATOR_SET_SCHEMA( Scan, 11, OpSchema() .SetDoc(scan_11_doc) .Input( 0, "initial_state_and_scan_inputs", "Initial values of the loop's N state variables followed by M scan_inputs", "V", OpSchema::Variadic, false) .Output( 0, "final_state_and_scan_outputs", "Final values of the loop's N state variables followed by K scan_outputs", "V", OpSchema::Variadic, false) .Attr( "body", "The graph run each iteration. It has N+M inputs: " "(loop state variables..., scan_input_elts...). It has N+K outputs: " "(loop state variables..., scan_output_elts...). Each " "scan_output is created by concatenating the value of the specified " "scan_output_elt value at the end of each iteration of the loop. It is an error" " if the dimensions of these values change across loop iterations.", AttributeProto::GRAPH, true) .Attr( "num_scan_inputs", "An attribute specifying the number of scan_inputs M. ", AttributeProto::INT, true) .Attr( "scan_input_directions", "An optional list of M flags. The i-th element of the list specifies the direction " "to be scanned for the i-th scan_input tensor: 0 indicates forward direction and 1 " "indicates reverse direction. " "If omitted, all scan_input tensors will be scanned in the forward direction.", AttributeProto::INTS, false) .Attr( "scan_output_directions", "An optional list of K flags, one for each scan_output. The i-th element of the list " "specifies whether the i-th scan_output should be constructed by appending or " "prepending a new value in each iteration: 0 indicates appending and 1 " "indicates prepending. " "If omitted, all scan_output tensors will be produced by appending a value " "in each iteration.", AttributeProto::INTS, false) .Attr( "scan_input_axes", "An optional list of M flags. The i-th element of the list specifies the axis " "to be scanned (the sequence axis) for the i-th scan_input. If omitted, 0 will " "be used as the scan axis for every scan_input. Negative value for an axis means " "counting dimensions from the back. Accepted range is [-r, r-1] where r = rank(input).", AttributeProto::INTS, false) .Attr( "scan_output_axes", "An optional list of K flags. The i-th element of the list specifies the axis " "for the i-th scan_output. The scan outputs are accumulated along the specified " "axis. If omitted, 0 will be used as the scan axis for every scan_output. " "Negative value for an axis means counting dimensions from the back. Accepted " "range is [-r, r-1].", AttributeProto::INTS, false) .TypeConstraint("I", {"tensor(int64)"}, "Int64 tensor") .TypeConstraint("V", OpSchema::all_tensor_types(), "All Tensor types") .TypeAndShapeInferenceFunction(ScanInferenceFunction)); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/defs/sequence/0000775000000000000000000000000013655345213014723 5ustar rootrootonnx-1.7.0/onnx/defs/sequence/defs.cc0000664000000000000000000005445713655345213016172 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #include "onnx/defs/function.h" #include "onnx/defs/schema.h" #include #include namespace ONNX_NAMESPACE { static const char* SequenceEmpty_ver11_doc = R"DOC( Construct an empty tensor sequence, with given data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SequenceEmpty, 11, OpSchema() .SetDoc(SequenceEmpty_ver11_doc) .Attr( "dtype", "(Optional) The data type of the tensors in the output sequence. " "The default type is 'float'.", AttributeProto::INT, OPTIONAL_VALUE) .Output( 0, "output", "Empty sequence.", "S") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { const auto* attr_proto = ctx.getAttribute("dtype"); auto elem_type = TensorProto::FLOAT; if (nullptr != attr_proto) { if (!attr_proto->has_i()) { fail_type_inference( "Attribute dtype should be of integer type and specify a type."); } auto attr_value = attr_proto->i(); elem_type = static_cast(attr_value); } ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_tensor_type() ->set_elem_type(elem_type); })); static const char* SequenceConstruct_ver11_doc = R"DOC( Construct a tensor sequence containing 'inputs' tensors. All tensors in 'inputs' must have the same data type. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SequenceConstruct, 11, OpSchema() .SetDoc(SequenceConstruct_ver11_doc) .Input( 0, "inputs", "Tensors.", "T", OpSchema::Variadic) .Output( 0, "output_sequence", "Sequence enclosing the input tensors.", "S") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input types to any tensor type.") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { const size_t numInputs = ctx.getNumInputs(); if (numInputs < 1) { fail_type_inference("SequenceConstruct is expected to have at least 1 input."); } std::vector input_elem_types; for (size_t i = 0; i < numInputs; ++i) { input_elem_types.emplace_back(ctx.getInputType(i)->tensor_type().elem_type()); } if (std::adjacent_find(input_elem_types.begin(), input_elem_types.end(), std::not_equal_to()) != input_elem_types.end()) { // not all input elem types are the same. fail_type_inference("Element type of inputs are expected to be the same."); } auto* output_tensor_type = ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_tensor_type(); output_tensor_type->set_elem_type(static_cast(input_elem_types[0])); if (!hasNInputShapes(ctx, static_cast(numInputs))) { return; } *(output_tensor_type->mutable_shape()) = ctx.getInputType(0)->tensor_type().shape(); for (size_t i = 1; i < numInputs; ++i) { const auto& input_shape = ctx.getInputType(i)->tensor_type().shape(); UnionShapeInfo(input_shape, *output_tensor_type); } })); static const char* SequenceInsert_ver11_doc = R"DOC( Outputs a tensor sequence that inserts 'tensor' into 'input_sequence' at 'position'. 'tensor' must have the same data type as 'input_sequence'. Accepted range for 'position' is in `[-n, n]`, where `n` is the number of tensors in 'input_sequence'. Negative value means counting positions from the back. 'position' is optional, by default it inserts 'tensor' to the back of 'input_sequence'. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SequenceInsert, 11, OpSchema() .SetDoc(SequenceInsert_ver11_doc) .Input( 0, "input_sequence", "Input sequence.", "S") .Input( 1, "tensor", "Input tensor to be inserted into the input sequence.", "T") .Input( 2, "position", "Position in the sequence where the new tensor is inserted. " "It is optional and default is to insert to the back of the sequence. " "Negative value means counting positions from the back. " "Accepted range in `[-n, n]`, " "where `n` is the number of tensors in 'input_sequence'. " "It is an error if any of the index values are out of bounds. " "It must be a scalar(tensor of empty shape).", "I", OpSchema::Optional) .Output( 0, "output_sequence", "Output sequence that contains the inserted tensor at given position.", "S") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain to any tensor type.") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain to any tensor type.") .TypeConstraint( "I", {"tensor(int32)", "tensor(int64)"}, "Constrain position to integral tensor. It must be a scalar(tensor of empty shape).") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { const auto seq_elem_type = ctx.getInputType(0)->sequence_type().elem_type().tensor_type().elem_type(); const auto tensor_elem_type = ctx.getInputType(1)->tensor_type().elem_type(); if (seq_elem_type != tensor_elem_type) { fail_type_inference( "Input Sequence and Tensor are expected to have the same elem type. Sequence=", seq_elem_type, " Tensor=", tensor_elem_type); } auto *output_tensor_type = ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_tensor_type(); output_tensor_type->set_elem_type(seq_elem_type); if (!hasInputShape(ctx, 0) || !hasInputShape(ctx, 1)) { return; } *(output_tensor_type->mutable_shape()) = ctx.getInputType(0)->sequence_type().elem_type().tensor_type().shape(); UnionShapeInfo(ctx.getInputType(1)->tensor_type().shape(), *output_tensor_type); })); static const char* SequenceAt_ver11_doc = R"DOC( Outputs a tensor copy from the tensor at 'position' in 'input_sequence'. Accepted range for 'position' is in `[-n, n - 1]`, where `n` is the number of tensors in 'input_sequence'. Negative value means counting positions from the back. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SequenceAt, 11, OpSchema() .SetDoc(SequenceAt_ver11_doc) .Input( 0, "input_sequence", "Input sequence.", "S") .Input( 1, "position", "Position of the tensor in the sequence. " "Negative value means counting positions from the back. " "Accepted range in `[-n, n - 1]`, " "where `n` is the number of tensors in 'input_sequence'. " "It is an error if any of the index values are out of bounds. " "It must be a scalar(tensor of empty shape).", "I") .Output( 0, "tensor", "Output tensor at the specified position in the input sequence.", "T") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain to any tensor type.") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain to any tensor type.") .TypeConstraint( "I", {"tensor(int32)", "tensor(int64)"}, "Constrain position to integral tensor. It must be a scalar(tensor of empty shape).") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { ctx.getOutputType(0)->CopyFrom(ctx.getInputType(0)->sequence_type().elem_type()); })); static const char* SequenceErase_ver11_doc = R"DOC( Outputs a tensor sequence that removes the tensor at 'position' from 'input_sequence'. Accepted range for 'position' is in `[-n, n - 1]`, where `n` is the number of tensors in 'input_sequence'. Negative value means counting positions from the back. 'position' is optional, by default it erases the last tensor from 'input_sequence'. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SequenceErase, 11, OpSchema() .SetDoc(SequenceErase_ver11_doc) .Input( 0, "input_sequence", "Input sequence.", "S") .Input( 1, "position", "Position of the tensor in the sequence. " "Negative value means counting positions from the back. " "Accepted range in `[-n, n - 1]`, " "where `n` is the number of tensors in 'input_sequence'. " "It is an error if any of the index values are out of bounds. " "It must be a scalar(tensor of empty shape).", "I", OpSchema::Optional) .Output( 0, "output_sequence", "Output sequence that has the tensor at the specified position removed.", "S") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain to any tensor type.") .TypeConstraint( "I", {"tensor(int32)", "tensor(int64)"}, "Constrain position to integral tensor. It must be a scalar(tensor of empty shape).") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { ctx.getOutputType(0)->CopyFrom(*ctx.getInputType(0)); })); static const char* SequenceLength_ver11_doc = R"DOC( Produces a scalar(tensor of empty shape) containing the number of tensors in 'input_sequence'. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SequenceLength, 11, OpSchema() .SetDoc(SequenceLength_ver11_doc) .Input( 0, "input_sequence", "Input sequence.", "S") .Output( 0, "length", "Length of input sequence. It must be a scalar(tensor of empty shape).", "I") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain to any tensor type.") .TypeConstraint( "I", {"tensor(int64)"}, "Constrain output to integral tensor. It must be a scalar(tensor of empty shape).") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto* output_tensor_type = ctx.getOutputType(0)->mutable_tensor_type(); output_tensor_type->set_elem_type(TensorProto::INT64); output_tensor_type->mutable_shape()->Clear(); })); // Updated operators that consume/produce sequence of tensors. static const char* SplitToSequence_ver11_doc = R"DOC(Split a tensor into a sequence of tensors, along the specified 'axis'. Lengths of the parts can be specified using argument 'split'. 'split' must contain only positive numbers. 'split' is either a scalar (tensor of empty shape), or a 1-D tensor. If 'split' is a scalar, then 'input' will be split into equally sized chunks(if possible). Last chunk will be smaller if the 'input' size along the given axis 'axis' is not divisible by 'split'. Otherwise, the tensor is split into 'size(split)' chunks, with lengths of the parts on 'axis' specified in 'split'. In this scenario, the sum of entries in 'split' must be equal to the dimension size of input tensor on 'axis'. )DOC"; ONNX_OPERATOR_SET_SCHEMA( SplitToSequence, 11, OpSchema() .Input(0, "input", "The tensor to split", "T") .Input( 1, "split", "Length of each output. " "It can be either a scalar(tensor of empty shape), or a 1-D tensor. All values must be >= 0. ", "I", OpSchema::Optional) .Output( 0, "output_sequence", "One or more outputs forming a sequence of tensors after splitting", "S") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain input types to all tensor types.") .TypeConstraint( "I", {"tensor(int32)", "tensor(int64)"}, "Constrain split size to integral tensor.") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain output types to all tensor types.") .Attr( "axis", "Which axis to split on. " "A negative value means counting dimensions from the back. Accepted range is [-rank, rank-1].", AttributeProto::INT, static_cast(0)) .Attr( "keepdims", "Keep the split dimension or not. Default 1, which means we keep split dimension. " "If input 'split' is specified, this attribute is ignored.", AttributeProto::INT, static_cast(1)) .SetDoc(SplitToSequence_ver11_doc) .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto elem_type = ctx.getInputType(0)->tensor_type().elem_type(); ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_tensor_type() ->set_elem_type(elem_type); if (!ctx.getInputType(0)->tensor_type().has_shape()) { return; } const auto& inputShape = ctx.getInputType(0)->tensor_type().shape(); int r = inputShape.dim_size(); int axis = static_cast(getAttribute(ctx, "axis", 0)); if (axis < -r || axis > r - 1) { fail_type_inference( "Invalid value of attribute 'axis'. Rank=", r, " Value=", axis); } if (axis < 0) { axis += r; } size_t num_inputs = ctx.getNumInputs(); int64_t splitSize = 1; int64_t keepdims = 1; if (num_inputs == 1) { // input split is omitted, default to split by 1. auto attr_proto = ctx.getAttribute("keepdims"); if (attr_proto) { keepdims = attr_proto->i(); } } else { splitSize = [&]() -> int64_t { // Need input split shape info and initializer data to infer split sizes. if (!ctx.getInputType(1)->tensor_type().has_shape()) { return -1; } const TensorProto* splitInitializer = ctx.getInputData(1); if (nullptr == splitInitializer || !splitInitializer->has_data_type()) { return -1; } std::vector splitSizes; if (splitInitializer->data_type() == TensorProto::INT64) { const auto& data = ParseData(splitInitializer); splitSizes.insert(splitSizes.end(), data.begin(), data.end()); } else if (splitInitializer->data_type() == TensorProto::INT32) { const auto& data = ParseData(splitInitializer); splitSizes.insert(splitSizes.end(), data.begin(), data.end()); } else { // unaccepted data type fail_shape_inference( "Only supports `int32_t` or `int64_t` inputs for split"); } if (splitSizes.size() == 0) { fail_shape_inference( "Input 'split' can not be empty."); } const auto& splitDim = inputShape.dim(axis); if (!splitDim.has_dim_value()) { // Unable to verify nor infer exact split dimension size. return -1; } int64_t splitDimValue = splitDim.dim_value(); const auto& splitShape = ctx.getInputType(1)->tensor_type().shape(); if (splitShape.dim_size() == 0) { // split is scalar if (splitDimValue % splitSizes[0] == 0) { // all output chunks have the same shape, assign that to output sequence shape. return splitSizes[0]; } return -1; } else { // split is 1-D tensor int64_t splitSizesSum = std::accumulate(splitSizes.begin(), splitSizes.end(), (int64_t)0); if (splitDimValue != splitSizesSum) { fail_shape_inference( "Sum of split values not equal to 'input' dim size on 'axis'. 'axis' dim size=", splitDimValue, " sum of split values=", splitSizesSum); } if (std::adjacent_find(splitSizes.begin(), splitSizes.end(), std::not_equal_to()) == splitSizes.end()) { // all split sizes are the same. return splitSizes[0]; } return -1; } }(); } if (keepdims) { auto* outputShape = ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_tensor_type() ->mutable_shape(); *outputShape = inputShape; auto* dim = outputShape->mutable_dim(axis); // Tensors in sequence could not have different shapes explicitly. // Only assign dim_value when all chunks have the same shape. if (splitSize > 0) { dim->set_dim_value(splitSize); } else { dim->clear_dim_value(); dim->clear_dim_param(); } } else { TensorShapeProto* outputShape = ctx.getOutputType(0) ->mutable_sequence_type() ->mutable_elem_type() ->mutable_tensor_type() ->mutable_shape(); for (int i = 0; i < inputShape.dim_size(); ++i) { if (i != axis) { auto* dim = outputShape->add_dim(); dim->CopyFrom(inputShape.dim(i)); } } } })); static const char* ConcatFromSequence_ver11_doc = R"DOC( Concatenate a sequence of tensors into a single tensor. All input tensors must have the same shape, except for the dimension size of the axis to concatenate on. By default 'new_axis' is 0, the behavior is similar to numpy.concatenate. When 'new_axis' is 1, the behavior is similar to numpy.stack. )DOC"; ONNX_OPERATOR_SET_SCHEMA( ConcatFromSequence, 11, OpSchema() .Attr( "axis", "Which axis to concat on. Accepted range in `[-r, r - 1]`, " "where `r` is the rank of input tensors. " "When `new_axis` is 1, accepted range is `[-r - 1, r]`. ", AttributeProto::INT) .Attr( "new_axis", "Insert and concatenate on a new axis or not, " "default 0 means do not insert new axis.", AttributeProto::INT, static_cast(0)) .SetDoc(ConcatFromSequence_ver11_doc) .Input( 0, "input_sequence", "Sequence of tensors for concatenation", "S") .Output(0, "concat_result", "Concatenated tensor", "T") .TypeConstraint( "S", OpSchema::all_tensor_sequence_types(), "Constrain input types to any tensor type.") .TypeConstraint( "T", OpSchema::all_tensor_types(), "Constrain output types to any tensor type.") .TypeAndShapeInferenceFunction([](InferenceContext& ctx) { auto elem_type = ctx.getInputType(0)->sequence_type().elem_type().tensor_type().elem_type(); ctx.getOutputType(0)->mutable_tensor_type()->set_elem_type(elem_type); if (!hasInputShape(ctx, 0)) { return; } auto axis_attr = ctx.getAttribute("axis"); if (!axis_attr) { fail_shape_inference("Required attribute axis is missing"); } int axis = static_cast(axis_attr->i()); int new_axis = 0; auto new_axis_attr = ctx.getAttribute("new_axis"); if (new_axis_attr) { new_axis = static_cast(new_axis_attr->i()); } const auto& input_shape = ctx.getInputType(0)->sequence_type().elem_type().tensor_type().shape(); auto rank = input_shape.dim_size(); if (1 != new_axis && 0 != new_axis) { fail_shape_inference("new_axis must be either 0 or 1"); } auto upper_bound = 1 == new_axis ? rank : rank - 1; auto lower_bound = 1 == new_axis ? -rank - 1 : -rank; if (axis < lower_bound || axis > upper_bound) { fail_shape_inference( "Invalid value of attribute 'axis'. Accepted range=[", lower_bound, ", ", upper_bound, "], Value=", axis); } if (axis < 0) { axis += (upper_bound + 1); } auto* output_shape = ctx.getOutputType(0) ->mutable_tensor_type() ->mutable_shape(); for (int i = 0; i <= upper_bound; ++i) { output_shape->add_dim(); if (i != axis) { output_shape->mutable_dim(i)->CopyFrom(input_shape.dim((i > axis && new_axis) ? i - 1 : i)); } } })); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/0000775000000000000000000000000013655345213014214 5ustar rootrootonnx-1.7.0/onnx/optimizer/pass_registry.h0000664000000000000000000000643513655345213017273 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/common/ir.h" #include "onnx/common/ir_pb_converter.h" #include "onnx/common/stl_backports.h" #include "onnx/optimizer/passes/eliminate_deadend.h" #include "onnx/optimizer/passes/eliminate_identity.h" #include "onnx/optimizer/passes/eliminate_nop_dropout.h" #include "onnx/optimizer/passes/eliminate_nop_monotone_argmax.h" #include "onnx/optimizer/passes/eliminate_nop_pad.h" #include "onnx/optimizer/passes/eliminate_nop_transpose.h" #include "onnx/optimizer/passes/eliminate_unused_initializer.h" #include "onnx/optimizer/passes/extract_constant_to_initializer.h" #include "onnx/optimizer/passes/fuse_add_bias_into_conv.h" #include "onnx/optimizer/passes/fuse_bn_into_conv.h" #include "onnx/optimizer/passes/fuse_consecutive_concats.h" #include "onnx/optimizer/passes/fuse_consecutive_log_softmax.h" #include "onnx/optimizer/passes/fuse_consecutive_reduce_unsqueeze.h" #include "onnx/optimizer/passes/fuse_consecutive_squeezes.h" #include "onnx/optimizer/passes/fuse_consecutive_transposes.h" #include "onnx/optimizer/passes/fuse_matmul_add_bias_into_gemm.h" #include "onnx/optimizer/passes/fuse_pad_into_conv.h" #include "onnx/optimizer/passes/fuse_transpose_into_gemm.h" #include "onnx/optimizer/passes/lift_lexical_references.h" #include "onnx/optimizer/passes/nop.h" #include "onnx/optimizer/passes/split.h" #include "onnx/proto_utils.h" #include #include namespace ONNX_NAMESPACE { namespace optimization { // Registry containing all passes available in ONNX. struct GlobalPassRegistry { std::map> passes; GlobalPassRegistry() { // Register the optimization passes to the optimizer. registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); registerPass(); } ~GlobalPassRegistry() { this->passes.clear(); } std::shared_ptr find(std::string pass_name) { auto it = this->passes.find(pass_name); ONNX_ASSERTM( it != this->passes.end(), "pass %s is unknown.", pass_name.c_str()); return it->second; } const std::vector GetAvailablePasses(); template void registerPass() { static_assert(std::is_base_of::value, "T must inherit from Pass"); std::shared_ptr pass(new T()); passes[pass->getPassName()] = pass; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/optimize.h0000664000000000000000000000273413655345213016233 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/common/ir.h" #include "onnx/common/ir_pb_converter.h" #include "onnx/common/stl_backports.h" #include "onnx/optimizer/pass_manager.h" #include "onnx/optimizer/pass_registry.h" #include "onnx/proto_utils.h" #include "vector" namespace ONNX_NAMESPACE { namespace optimization { struct Optimizer { static GlobalPassRegistry passes; public: Optimizer(const std::vector& names, const bool fixed_point); ~Optimizer(); ModelProto optimize(const ModelProto& mp_in) { std::shared_ptr g(ImportModelProto(mp_in)); if (g.get() == nullptr) { std::cerr << "Warning: onnx optimizer is unable to parse input model. " << "(The IR version of the ONNX model may be too old.)" << std::endl; // If we can't parse the file, just return the input. return mp_in; } ModelProto mp_out = PrepareOutput(mp_in); this->pass_manager->run(*g); ExportModelProto(&mp_out, g); return mp_out; } private: std::shared_ptr pass_manager; }; const std::vector GetAvailablePasses(); ModelProto Optimize( const ModelProto& mp_in, const std::vector& names); ModelProto OptimizeFixed( const ModelProto& mp_in, const std::vector& names); } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/0000775000000000000000000000000013655345213015512 5ustar rootrootonnx-1.7.0/onnx/optimizer/passes/fuse_add_bias_into_conv.h0000664000000000000000000001261613655345213022517 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // Z = Conv(X, Y) // B = Z + A // After: // B = Conv(X, Y, A) // // the pass can handle the following cases: // case 1: A is 1D tensor and A.dim[0] == Z.dim[1] // case 2: A is 1-element 1D tensor #include #include "onnx/common/assertions.h" #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseAddBiasIntoConv final : public PredicateBasedPass { explicit FuseAddBiasIntoConv() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_add_bias_into_conv"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kAdd && node->inputs()[0]->node()->kind() == kConv && node->inputs()[0]->node()->inputs().size() == 2; } bool runTransform(Node* n, Graph& graph, NodeDestroyType& destroy_current) override { // due to current broadcasting's constraint, Conv has to be the first // operand destroy_current = NodeDestroyType::DestroyZero; auto orig_conv = n->inputs()[0]; auto orig_bias = n->inputs()[1]; // check if bias is Const or in graph's initializers if (orig_bias->node()->kind() != kConstant && orig_bias->node()->kind() != kParam) { return false; } // check if conv is only used by Add if (orig_conv->uses().size() > 1) { return false; } auto conv_shape = orig_conv->sizes(); auto bias_shape = orig_bias->sizes(); auto weight_shape = orig_conv->node()->inputs()[1]->sizes(); int64_t M = -1; int64_t rank = -1; // try to get feature M and rank from conv_shape if (conv_shape.size() > 1 && conv_shape[1].is_int) { M = conv_shape[1].dim; rank = conv_shape.size(); } // try to get feature M and rank from weight_shape if (weight_shape.size() > 0 && weight_shape[0].is_int) { ONNX_ASSERT(M == -1 || M == weight_shape[0].dim); M = weight_shape[0].dim; ONNX_ASSERT( rank == -1 || rank == static_cast(weight_shape.size())); rank = weight_shape.size(); } int64_t num_el = 1; for (int i = 0; i < static_cast(bias_shape.size()); ++i) { if (bias_shape[i].is_int) { num_el *= bias_shape[i].dim; } else { num_el = -1; return false; } } if (M == -1 || num_el == -1) { // No enough information, bail out return false; } if (rank < static_cast(bias_shape.size())) { return false; } if (num_el == 1) { if (orig_bias->node()->kind() != kParam && orig_conv->node()->isBefore(orig_bias->node())) { orig_bias->node()->moveBefore(orig_conv->node()); } Value* conv_3rd_input = orig_bias; if (bias_shape.size() > 1) { Node* squeeze = graph.create(kSqueeze, 1); std::vector axes(bias_shape.size() - 1); std::iota(axes.begin(), axes.end(), 0); squeeze->is_(kaxes, std::move(axes)); squeeze->addInput(conv_3rd_input); conv_3rd_input = squeeze->output(); squeeze->insertBefore(orig_conv->node()); } if (M > 1) { Node* constant = graph.create(kConstant, 1); Tensor t; t.sizes().push_back(static_cast(1)); t.int64s().push_back(M); t.elem_type() = TensorProto_DataType_INT64; Symbol sym = Symbol("value"); constant->t_(sym, t); std::vector s = {1}; constant->output()->setSizes(s); constant->output()->setElemType(TensorProto_DataType_INT64); constant->insertBefore(orig_conv->node()); Node* tile = graph.create(kTile, 1); tile->addInput(conv_3rd_input); tile->addInput(constant->output()); conv_3rd_input = tile->output(); tile->insertBefore(orig_conv->node()); } orig_conv->node()->addInput(conv_3rd_input); } else if (rank > static_cast(bias_shape.size()) + 1) { return false; } else if ( num_el == M && bias_shape[1 + bias_shape.size() - static_cast(rank)].dim == M) { ONNX_ASSERT(bias_shape.size() > 1); if (orig_bias->node()->kind() != kParam && orig_conv->node()->isBefore(orig_bias->node())) { orig_bias->node()->moveBefore(orig_conv->node()); } Node* squeeze = graph.create(kSqueeze, 1); std::vector axes(bias_shape.size()); std::iota(axes.begin(), axes.end(), static_cast(0)); axes.erase( axes.begin() + (1 + bias_shape.size() - static_cast(rank))); squeeze->is_(kaxes, std::move(axes)); squeeze->addInput(orig_bias); squeeze->insertBefore(orig_conv->node()); orig_conv->node()->addInput(squeeze->output()); } else { return false; } if (orig_conv->sizes().size() == 0 && n->output()->sizes().size() > 0) { orig_conv->setSizes(n->output()->sizes()); } if (n->output()->elemType() != TensorProto_DataType_UNDEFINED) { orig_conv->setElemType(n->output()->elemType()); } n->replaceAllUsesWith(orig_conv->node()); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_bn_into_conv.h0000664000000000000000000001610113655345213021361 0ustar rootroot // ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // conv = Conv() // bn = BatchNormalization() // // After: // bn is deleted // new inputs/initializers to conv are added to graph // any no longer used inputs/initializers are erased from graph // // this pass can handle the case satisfy all following conditions: // condition 1: Run in testing mode // condition 2: Inputs 1 - 4 of bn are all initializer_size // condition 3: Output of initial conv has no other uses // condition 3: Currently works for only DOUBLE, FLOAT32 tensor types // // Formula for transformation // $$ X_{bn} = \frac{s(X - m)}{\sqrt{\sigma + \epsilon}} + b_{bn}$$ // $$ X_{conv} = X * W + b_{conv} $$ // thus, substituting $X$ with $X_{conv}$ in the BN equation we get: // $$X_{bn} = X * \frac{sW}{\sqrt{\sigma + \epsilon}} + \frac{s(b_{conv} - // m)}{\sqrt{\sigma + \epsilon}} + b_{bn}$$ or // $$ W' = W\frac{s}{\sqrt{\sigma + \epsilon}}$$ // $$ b' = (b_{conv} - m)\frac{s}{\sqrt{\sigma + \epsilon}} + b_{bn}$$ #include "onnx/common/assertions.h" #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { // TODO: Currently broken for complex values and float16 struct FuseBNIntoConv final : public PredicateBasedPass { explicit FuseBNIntoConv() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_bn_into_conv"; } void replace_inputs(Tensor& W, Tensor& b, Node* conv, Graph& graph) { Value* new_W_value = graph.addInitializerAndInput(W); Value* old_W_value = conv->inputs()[1]; conv->replaceInput(1, new_W_value); if (old_W_value->uses().size() == 0) { graph.eraseInitializerAndInput(old_W_value); } if (conv->inputs().size() == 3) { Value* new_b_value = graph.addInitializerAndInput(b); Value* old_b_value = conv->inputs()[2]; conv->replaceInput(2, new_b_value); if (old_b_value->uses().size() == 0) { graph.eraseInitializerAndInput(old_b_value); } } else { Value* new_b_value = graph.addInitializerAndInput(b); conv->addInput(new_b_value); } } bool modify_conv(Node* conv, Node* bn, Graph& graph) { const auto& bn_inputs = bn->inputs(); const auto& conv_inputs = conv->inputs(); auto end_iter = graph.initializers().end(); auto s_iter = graph.getInitializer(bn_inputs[1]->uniqueName()); auto bbn_iter = graph.getInitializer(bn_inputs[2]->uniqueName()); auto m_iter = graph.getInitializer(bn_inputs[3]->uniqueName()); auto var_iter = graph.getInitializer(bn_inputs[4]->uniqueName()); auto W_iter = graph.getInitializer(conv_inputs[1]->uniqueName()); if (s_iter == end_iter || bbn_iter == end_iter || m_iter == end_iter || var_iter == end_iter || W_iter == end_iter) { return false; } ONNX_ASSERT(s_iter->sizes().size() == 1); ONNX_ASSERT( bbn_iter->sizes().size() == 1 && bbn_iter->sizes()[0] == s_iter->sizes()[0]); ONNX_ASSERT( m_iter->sizes().size() == 1 && m_iter->sizes()[0] == s_iter->sizes()[0]); ONNX_ASSERT( var_iter->sizes().size() == 1 && var_iter->sizes()[0] == s_iter->sizes()[0]); ONNX_ASSERT( W_iter->sizes().size() > 2 && W_iter->sizes()[0] == s_iter->sizes()[0]); ONNX_ASSERT( s_iter->elem_type() == bbn_iter->elem_type() && s_iter->elem_type() == m_iter->elem_type() && s_iter->elem_type() == var_iter->elem_type() && s_iter->elem_type() == W_iter->elem_type()); if (s_iter->elem_type() != ONNX_NAMESPACE::TensorProto_DataType_FLOAT && s_iter->elem_type() != ONNX_NAMESPACE::TensorProto_DataType_DOUBLE) { return false; } Tensor bc; if (conv_inputs.size() == 3) { auto bc_iter = graph.getInitializer(conv_inputs[2]->uniqueName()); if (bc_iter == end_iter) { return false; } bc = *bc_iter; ONNX_ASSERT( bc.sizes().size() == 1 && bc.sizes()[0] == s_iter->sizes()[0]); } Tensor s = *s_iter; const Tensor& bbn = *bbn_iter; const Tensor& m = *m_iter; Tensor var = *var_iter; Tensor W = *W_iter; float epsilon = bn->hasAttribute(kepsilon) ? (float)bn->f(kepsilon) : 1e-5f; Tensor eps; #define DO_COMPUTATION(TENSOR_TYPE, vec) \ eps.sizes().push_back(s.sizes()[0]); \ eps.elem_type() = ONNX_NAMESPACE::TensorProto_DataType_##TENSOR_TYPE; \ for (int64_t i = 0; i < eps.sizes()[0]; ++i) { \ eps.vec().push_back(epsilon); \ } \ if (conv_inputs.size() != 3) { \ bc.sizes().push_back(s.sizes()[0]); \ bc.elem_type() = ONNX_NAMESPACE::TensorProto_DataType_##TENSOR_TYPE; \ for (int64_t i = 0; i < eps.sizes()[0]; ++i) { \ bc.vec().push_back(0.f); \ } \ } \ var.add(eps); \ var.sqrt(); \ s.divide(var); \ W.scale_by_first_dim(s); \ bc.subtract(m); \ bc.multiply(s); \ bc.add(bbn); switch (s.elem_type()) { case ONNX_NAMESPACE::TensorProto_DataType_FLOAT: { DO_COMPUTATION(FLOAT, floats) break; } case ONNX_NAMESPACE::TensorProto_DataType_DOUBLE: { DO_COMPUTATION(DOUBLE, doubles) break; } default: return false; } #undef DO_COMPUTATION replace_inputs(W, bc, conv, graph); return true; } bool patternMatchPredicate(Node* node) override { return node->kind() == kBatchNormalization && node->inputs()[0]->node()->kind() == kConv; } bool runTransform(Node* n, Graph& graph, NodeDestroyType& destroy_current) override { Node* bn = n; Node* conv = n->inputs()[0]->node(); auto origInput = bn->inputs()[0]; if (origInput->uses().size() > 1 || bn->outputs().size() > 1 || !modify_conv(conv, bn, graph)) { destroy_current = NodeDestroyType::DestroyZero; return false; } for (int i = 4; i >= 1; --i) { if (bn->inputs()[i]->uses().size() == 1) { auto input = bn->inputs()[i]; bn->removeInput(i); graph.eraseInitializerAndInput(input); } } bn->output()->replaceAllUsesWith(origInput); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/lift_lexical_references.h0000664000000000000000000001701013655345213022522 0ustar rootroot#pragma once #include #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { // Lift lexically-scoped references within control operators to be inputs of the // ops themselves. This transformation yields a graph that does not conform to // the ONNX spec. // // The purpose of this pass is to expose the data dependencies within control // blocks for frameworks that use those dependencies to schedule parallel // execution. e.g. caffe2 graph execution. // // Example: // ******************************** Before ************************************* // graph test (%X[FLOAT, 5]) { // %Y = Identity(%X) // %trip_count = Constant[value = ]() // %condition = Constant[value = ]() // %Y2, %Y3 = Loop[body = ](%trip_count, %condition, %) // return %Y, %Y2 // } // // graph body_graph (%i[INT32, scalar], %cond[BOOL, scalar]) { // %_Y2 = Identity(%X) // %_Y3 = Identity(%Y) // return %cond, %_Y2, %_Y3 // } // // ******************************** After ************************************** // graph test (%X[FLOAT, 5]) { // %Y = Identity(%X) // %trip_count = Constant[value = ]() // %condition = Constant[value = ]() // %Y2, %Y3 = Loop[__control_inputs = ['X', 'Y'], body = ](%trip_count, %condition, %) // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // return %Y, %Y2 // } // // graph body_graph (%i[INT32, scalar], %cond[BOOL, scalar]) { // %_Y2 = Identity(%X) // %_Y3 = Identity(%Y) // return %cond, %_Y2, %_Y3 // } // // ******************************** Continue Docs******************************* // // The algorithm is roughly: // symbol_table_stack = empty stack of symbol tables // // liftreferences(graph) // -> a set of unresolved reference strings: // unresolved_references = {} // // symbol_table_stack.push(new symbol table containing inputs for this // sub-graph) for each node in the graph: // for input in node.inputs: // if input is not in this frame: // unresolved_references.insert(input) // if node is a control flow operator: // for each sub-graph g: // for each output in g's body: // if output is defined in current scope: // control_inputs.insert(output) // refs = liftreferences(g) // for each ref in refs: // if ref is in this frame or any parent frame (control_inputs): // control_inputs.insert(ref) // else: // unresolved_references.insert(ref) // set the control inputs attribute to the node // for output in node.outputs: // symbol_table_stack.top()[output] = Value* // return unresolved_references struct LiftLexicalReferences : public FullGraphBasedPass { explicit LiftLexicalReferences() : FullGraphBasedPass( PassType::Separate, PassEfficiency::Complete, PassOptimizationType::Memory) {} std::string getPassName() const override { return "lift_lexical_references"; } PassAnalysisType getPassAnalysisType() const override { return PassAnalysisType::Empty; } using ValueTable = std::unordered_map; // Environment stack, please to store value table and // controlled inputs struct Environment { Environment(std::shared_ptr next = nullptr) : next(next) {} std::shared_ptr next; Value* findInThisFrame(const std::string& name) { auto it = value_table.find(name); if (it != value_table.end()) { return it->second; } return nullptr; } Value* findInParentFrame(const std::string& name) { return next ? next->findInAnyFrame(name) : nullptr; } Value* findInAnyFrame(const std::string& name) { for (auto runner = this; runner; runner = runner->next.get()) { if (auto r = runner->findInThisFrame(name)) { return r; } } return nullptr; } void setVar(const std::string& name, Value* value) { value_table[name] = value; } private: ValueTable value_table; }; std::shared_ptr environment_stack; // environment stack helper void pushFrame() { environment_stack = std::make_shared(environment_stack); } std::shared_ptr popFrame() { auto old_frame = environment_stack; environment_stack = environment_stack->next; return old_frame; } std::set liftReferences(Graph* g) { std::set unresolved_references; pushFrame(); for (auto& inp : g->inputs()) { environment_stack->setVar(inp->uniqueName(), inp); } for (auto* n : g->nodes()) { // Skip optional input/captured value node. if (n->kind() == ONNX_NAMESPACE::kUndefined || n->kind() == ONNX_NAMESPACE::kCaptured) { continue; } for (auto* inp : n->inputs()) { // Empty string is 0-input variadic argument. Skip that one. if (!inp->uniqueName().empty() && !environment_stack->findInThisFrame(inp->uniqueName())) { unresolved_references.insert(inp->uniqueName()); } } std::set local_unresolved; // if a graph body output has already already been emitted outside of the // subgraph scope, then it must be added as an input to the subgraph auto add_subgraph_outputs = [&](Graph* body_graph) { for (auto* out : body_graph->outputs()) { if (environment_stack->findInAnyFrame(out->uniqueName())) { local_unresolved.insert(out->uniqueName()); } } }; if (n->kind() == ONNX_NAMESPACE::kLoop) { auto* body_graph = n->g(ONNX_NAMESPACE::kbody).get(); local_unresolved = liftReferences(body_graph); add_subgraph_outputs(body_graph); } else if (n->kind() == ONNX_NAMESPACE::kIf) { auto* then_graph = n->g(ONNX_NAMESPACE::kthen_branch).get(); add_subgraph_outputs(then_graph); auto then_unresolved = liftReferences(then_graph); local_unresolved.insert(then_unresolved.begin(), then_unresolved.end()); auto* else_graph = n->g(ONNX_NAMESPACE::kelse_branch).get(); add_subgraph_outputs(else_graph); auto else_unresolved = liftReferences(else_graph); local_unresolved.insert(else_unresolved.begin(), else_unresolved.end()); } std::vector control_inputs; for (auto& unresolved : local_unresolved) { if (environment_stack->findInAnyFrame(unresolved)) { control_inputs.push_back(unresolved); } else { unresolved_references.insert(unresolved); } } // Create this attribute so the backend knows how many of these inputs // are simply there for control dependencies if (!control_inputs.empty()) { n->ss_(ONNX_NAMESPACE::k__control_inputs, std::move(control_inputs)); } for (auto* out : n->outputs()) { environment_stack->setVar(out->uniqueName(), out); } } popFrame(); return unresolved_references; } std::shared_ptr runPass(Graph& graph) override { auto unresolved = liftReferences(&graph); if (unresolved.size()) { std::string errmsg = "Unresolved value references: "; for (auto& ref : unresolved) { errmsg += ref + ","; } throw std::runtime_error(errmsg); } return std::shared_ptr(new PostPassAnalysis()); } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_matmul_add_bias_into_gemm.h0000664000000000000000000000653213655345213024056 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // Z = MatMul(X, Y) // A = Z + Bias // After: // A = Gemm(X, Y, Bias) // // the pass can handle the case when: // case 1: Bias is 1D tensor and Bias.dim[0] == Z.dim[1] // case 2: Bias is 2D tensor and Bias.dim[0] == Z.dim[0] or 1 // and Bias.dim[1] = Z.dim[1] #include #include "onnx/common/assertions.h" #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseMatMulAddBiasIntoGemm final : public PredicateBasedPass { explicit FuseMatMulAddBiasIntoGemm() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_matmul_add_bias_into_gemm"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kAdd && node->inputs()[0]->node()->kind() == kMatMul; } bool runTransform(Node* n, Graph& graph, NodeDestroyType& destroy_current) override { // due to current broadcasting's constraint, MatMul has to be the first // operand destroy_current = NodeDestroyType::DestroyZero; auto orig_matmul = n->inputs()[0]; auto orig_bias = n->inputs()[1]; // check if bias is Const or in graph's initializers if (orig_bias->node()->kind() != kConstant && orig_bias->node()->kind() != kParam) { return false; } // check if MatMul is only used by Add if (orig_matmul->uses().size() > 1) { return false; } auto x_shape = orig_matmul->node()->inputs()[0]->sizes(); auto y_shape = orig_matmul->node()->inputs()[1]->sizes(); int64_t z_N = -1; int64_t z_M = -1; // try to get feature N from x_shape if (static_cast(x_shape.size()) == 2 && x_shape[0].is_int) { z_N = x_shape[0].dim; } else { return false; } // try to get feature M from y_shape if (static_cast(y_shape.size()) == 2 && y_shape[1].is_int) { z_M = y_shape[1].dim; } else { return false; } // check if bias_shape is compatible auto bias_shape = orig_bias->sizes(); auto bias_dim = static_cast(bias_shape.size()); int64_t bias_N = -1; int64_t bias_M = -1; if (bias_dim == 1 && bias_shape[0].is_int) { bias_N = 1; bias_M = bias_shape[0].dim; } else if (bias_dim == 2 && bias_shape[0].is_int && bias_shape[1].is_int) { bias_N = bias_shape[0].dim; bias_M = bias_shape[1].dim; } else { return false; } if ((bias_N != z_N && bias_N != 1) || bias_M != z_M) { return false; } // proceed to fuse MatMul and Add into Gemm Node* gemm = graph.create(kGemm, orig_matmul->node()->inputs(), n->outputs().size()); gemm->addInput(n->inputs()[1]); for (int i = 0; i < static_cast(gemm->outputs().size()); ++i) { gemm->outputs()[i]->copyMetadata(n->outputs()[i]); } gemm->f_(kalpha, 1.0); gemm->f_(kbeta, 1.0); gemm->i_(ktransA, 0); gemm->i_(ktransB, 0); gemm->insertBefore(orig_matmul->node()); n->replaceAllUsesWith(gemm); destroy_current = NodeDestroyType::DestroyTwo; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_identity.h0000664000000000000000000000200013655345213021533 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct EliminateIdentity final : public PredicateBasedPass { explicit EliminateIdentity() : PredicateBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "eliminate_identity"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kIdentity; } bool runTransform(Node* node, Graph&, NodeDestroyType& destroy_current) override { if (node->output()->has_sizes()) { node->input()->setSizes(node->output()->sizes()); } node->output()->replaceAllUsesWith(node->input()); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_nop_monotone_argmax.h0000664000000000000000000000401013655345213023756 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { const std::unordered_set monotone_node_no_axis_kind{kLog, kExp, kSqrt}; const std::unordered_set monotone_node_axis_kind{kSoftmax, kLogSoftmax}; struct EliminateNopMonotoneArgmax final : public PredicateBasedPass { explicit EliminateNopMonotoneArgmax() : PredicateBasedPass( PassType::Nop, PassEfficiency::Partial, PassOptimizationType::Compute) {} std::string getPassName() const override { return "eliminate_nop_monotone_argmax"; } static inline bool satisfies_monotone_condition(int64_t axis, Node* node) { if (monotone_node_no_axis_kind.find(node->kind()) != monotone_node_no_axis_kind.end()) { return true; } if (monotone_node_axis_kind.find(node->kind()) != monotone_node_axis_kind.end()) { if (node->hasAttribute(kaxis)) { return axis == node->i(kaxis); } } return false; } bool patternMatchPredicate(Node* node) override { if (node->kind() == kArgMax) { if (node->hasAttribute(kaxis)) { auto node_axis = node->i(kaxis); return node->inputs().size() == 1 && satisfies_monotone_condition(node_axis, node->input()->node()); } } return false; } bool runTransform(Node* node, Graph&, NodeDestroyType&) override { Node* monotone_node = node->input()->node(); if (monotone_node->output()->uses().size() == 1) { monotone_node->output()->replaceAllUsesWith(monotone_node->input()); monotone_node->destroy(); return true; } return false; } }; } // namespace optimization } // namespace ONNX_NAMESPACEonnx-1.7.0/onnx/optimizer/passes/fuse_consecutive_transposes.h0000664000000000000000000000470513655345213023523 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseConsecutiveTransposes final : public PredicateBasedPass { explicit FuseConsecutiveTransposes() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_consecutive_transposes"; } // returns a vector `ret` such that transposing by `ret` is equivalent // to transposing by `t1` and then by `t2` std::vector compose_transposes( const std::vector& t1, const std::vector& t2) { ONNX_ASSERT(t1.size() == t2.size()); std::vector ret; ret.reserve(t1.size()); for (size_t i = 0; i < t1.size(); i++) { ONNX_ASSERT(t2[i] < static_cast(t1.size())); ONNX_ASSERT( t1[static_cast(t2[i])] < static_cast(t1.size())); ret.push_back(t1[static_cast(t2[i])]); } return ret; } bool patternMatchPredicate(Node* node) override { return node->kind() == kTranspose && node->input()->node()->kind() == kTranspose; } bool runTransform(Node* n, Graph&, NodeDestroyType& destroy_current) override { auto origInput = n->input(); if (!n->hasAttribute(kperm) && !origInput->node()->hasAttribute(kperm)) { // One special case (two consecutive transposes with no perm, // since we do not have the shape information here, we have // to eliminate two transpose together. if (n->output()->has_sizes()) { origInput->node()->input()->setSizes(n->output()->sizes()); } n->replaceAllUsesWith(origInput->node()->input()->node()); destroy_current = NodeDestroyType::DestroyTwo; return true; } if (!n->hasAttribute(kperm) || !origInput->node()->hasAttribute(kperm)) { destroy_current = NodeDestroyType::DestroyZero; return false; } n->is_( kperm, compose_transposes(origInput->node()->is(kperm), n->is(kperm))); n->replaceInput(0, origInput->node()->input()); if (origInput->uses().size() == 0) { origInput->node()->destroy(); } destroy_current = NodeDestroyType::DestroyZero; return false; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/extract_constant_to_initializer.h0000664000000000000000000000246713655345213024364 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // A = Constant() // After: // A is in the initializer list // // this pass can handle the case satisfy all following conditions: // condition 1: A is the output of a Constant node #include "onnx/common/assertions.h" #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct ExtractConstantToInitializer final : public PredicateBasedPass { explicit ExtractConstantToInitializer() : PredicateBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Memory) {} std::string getPassName() const override { return "extract_constant_to_initializer"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kConstant; } bool runTransform(Node* node, Graph& graph, NodeDestroyType& destroy_current) override { const auto name = node->output()->uniqueName(); Tensor t = node->t(kvalue); Value* new_init = graph.addInitializerAndInput(t, name); node->output()->replaceAllUsesWith(new_init); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_nop_pad.h0000664000000000000000000000434713655345213021342 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/defs/tensor_util.h" #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct EliminateNopPad final : public PredicateBasedPass { explicit EliminateNopPad() : PredicateBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "eliminate_nop_pad"; } static bool is_nop_pad(Node* node, Graph& graph) { if (node->hasAttribute(kpads)) { // opset 10 and below const auto& pads = node->is(kpads); for (size_t i = 0; i < pads.size(); i++) { // if pad constant_value is non-zero, this is not a nop pad if (pads[i] != 0) { return false; } } return true; } else { // opset 11 and above const auto& pads_name = node->inputs()[1]->uniqueName(); const auto pads_initializer = graph.getInitializer(pads_name); // 'pad' node has the 'pads' input which has not been initialized - // can't proceed with elimination if (pads_initializer == graph.initializers().end()) return false; // validate values within 'pads' if (pads_initializer->elem_type() == TensorProto::INT64) { const auto& pads = ParseData(&*pads_initializer); for (const auto& val : pads) { // if pad constant_value is non-zero, this is not a nop pad if (val != 0) { return false; } } return true; } } return false; } bool patternMatchPredicate(Node* node) override { return node->kind() == kPad; } bool runTransform(Node* node, Graph& graph, NodeDestroyType& destroy_current) override { if (!is_nop_pad(node, graph)) return false; if (node->output()->has_sizes()) { node->inputs()[0]->setSizes(node->output()->sizes()); } node->output()->replaceAllUsesWith(node->inputs()[0]); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_consecutive_reduce_unsqueeze.h0000664000000000000000000000513513655345213024673 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { const std::unordered_set reduction_operators{kReduceL1, kReduceL2, kReduceLogSum, kReduceLogSumExp, kReduceMax, kReduceMean, kReduceMin, kReduceProd, kReduceSum, kReduceSumSquare}; struct FuseConsecutiveReduceUnsqueeze final : public PredicateBasedPass { explicit FuseConsecutiveReduceUnsqueeze() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_consecutive_reduce_unsqueeze"; } bool patternMatchPredicate(Node* node) override { // check that the current node is of type Unsqueeze and has defined axes bool cur_node_check = node->kind() == kUnsqueeze && node->hasAttribute(kaxes); if (cur_node_check) { Node* prev_node = node->input()->node(); // check that the previous node a reduction operator and has defined // axes/keepdims bool reduction_node_check = reduction_operators.find(prev_node->kind()) != reduction_operators.end() && prev_node->hasAttribute(kaxes) && prev_node->hasAttribute(kkeepdims); if (reduction_node_check) { // insure that keepdims is set to false currently return prev_node->i(kkeepdims) == 0 && node->is(kaxes) == prev_node->is(kaxes); } } return false; } bool runTransform(Node* node, Graph&, NodeDestroyType& destroy_current) override { Node* reduction_op = node->input()->node(); // set keepdims flag to be true reduction_op->i_(kkeepdims, 1); // remove unnecessary unsqueeze reduction_op->output()->setSizes(node->output()->sizes()); reduction_op->output()->setElemType(node->output()->elemType()); node->output()->replaceAllUsesWith(node->input()); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/nop.h0000664000000000000000000000122613655345213016460 0ustar rootroot#pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct NopEmptyPass final : public FullGraphBasedPass { explicit NopEmptyPass() : FullGraphBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::None) {} std::string getPassName() const override { return "nop"; } PassAnalysisType getPassAnalysisType() const override { return PassAnalysisType::Empty; } std::shared_ptr runPass(Graph&) override { return std::make_shared(); } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_deadend.h0000664000000000000000000000240013655345213021272 0ustar rootroot // ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct EliminateDeadEnd final : public FullGraphBasedPass { explicit EliminateDeadEnd() : FullGraphBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "eliminate_deadend"; } PassAnalysisType getPassAnalysisType() const override { return PassAnalysisType::CountBased; } unsigned int EliminateDead(Graph& graph) { unsigned int nodes_removed = 0; auto nodes = graph.nodes().reverse(); for (auto it = nodes.begin(); it != nodes.end(); it++) { auto node = *it; if (!node->hasUses()) { nodes_removed++; it.destroyCurrent(); } } return nodes_removed; } std::shared_ptr runPass(Graph& graph) override { auto nodes_removed = this->EliminateDead(graph); return std::shared_ptr( new CountBasedPassAnalysis(this, nodes_removed, false, false)); } }; } // namespace optimization } // namespace ONNX_NAMESPACEonnx-1.7.0/onnx/optimizer/passes/fuse_consecutive_log_softmax.h0000664000000000000000000000307013655345213023636 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseConsecutiveLogSoftmax final : public PredicateBasedPass { explicit FuseConsecutiveLogSoftmax() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_consecutive_log_softmax"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kLog && node->input()->node()->kind() == kSoftmax && node->input()->uses().size() == 1; } bool runTransform( Node* log_node, Graph& graph, NodeDestroyType& destroy_current) override { Value* log_node_output = log_node->output(); Node* softmax_node = log_node->inputs()[0]->node(); Node* log_softmax_node = graph.create(kLogSoftmax, 1); // log_softmax_node construction log_softmax_node->i_(kaxis, softmax_node->i(kaxis)); log_softmax_node->addInput(softmax_node->input()); log_softmax_node->insertBefore(softmax_node); log_softmax_node->output()->setSizes(log_node_output->sizes()); log_softmax_node->output()->setElemType(log_node_output->elemType()); log_node->replaceAllUsesWith(log_softmax_node); log_node->removeAllInputs(); destroy_current = NodeDestroyType::DestroyTwo; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_unused_initializer.h0000664000000000000000000000466013655345213023626 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // A, B, C are in the initializer list // D = Add(B, C) // After: // B, C are in the initializer list and A is removed // D = Add(B, C) // // this pass can handle the case satisfy all following conditions: // condition 1: A is not used as any node's input // condition 2: A is not an output #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct EliminateUnusedInitializer final : public FullGraphBasedPass { explicit EliminateUnusedInitializer() : FullGraphBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Memory) {} std::string getPassName() const override { return "eliminate_unused_initializer"; } PassAnalysisType getPassAnalysisType() const override { return PassAnalysisType::Empty; } void erase_used_initializers( Graph& g, std::unordered_set* initializer_names) { for (auto output : g.outputs()) { initializer_names->erase(output->uniqueName()); } for (auto it = g.begin(); it != g.end(); ++it) { auto* n = *it; DescendOnGraphAttributesUnconstrained( n, [this, initializer_names](Graph& graph) { erase_used_initializers(graph, initializer_names); }); for (auto* input : n->inputs()) { initializer_names->erase(input->uniqueName()); } } } void eliminate_unused_initializer(Graph& graph) { std::unordered_set initializer_names( graph.initializer_names().begin(), graph.initializer_names().end()); erase_used_initializers(graph, &initializer_names); // remove initializer and input if need for (std::string name : initializer_names) { graph.eraseInitializer(name); auto iter = std::find_if( graph.inputs().begin(), graph.inputs().end(), [&name](Value* input) { return input->uniqueName() == name; }); if (iter != graph.inputs().end()) { graph.eraseInput(std::distance(graph.inputs().begin(), iter)); } } } std::shared_ptr runPass(Graph& graph) override { eliminate_unused_initializer(graph); return std::shared_ptr(new PostPassAnalysis()); } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_consecutive_concats.h0000664000000000000000000000523413655345213022752 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseConsecutiveConcats final : public PredicateBasedPass { explicit FuseConsecutiveConcats() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Partial, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_consecutive_concats"; } void insertInput(Node* node, size_t i, Value* value) { const auto input_size = node->inputs().size(); if (i == input_size) { node->addInput(value); } else { for (size_t j = input_size - 1; j >= i; j--) { Value* cur_input = node->input(j); if (j == input_size - 1) { node->addInput(cur_input); } else { node->replaceInput(j + 1, cur_input); } } node->replaceInput(i, value); } } bool patternMatchPredicate(Node* node) override { // we don't check if our concat node has inputs which are also concat nodes // because this requires a for loop through the inputs. If it turns out // there is then we still have to do a for loop in the runTransform portion // of the code. In order not to waste a loop we don't check the real pattern // match condition. return node->kind() == kConcat && node->hasAttribute(kaxis); } bool runTransform(Node* concat_node, Graph&, NodeDestroyType& destroy_current) override { destroy_current = NodeDestroyType::DestroyZero; bool transform_ran = false; for (size_t i = 0; i < concat_node->inputs().size(); i++) { Value* cur_input_value = concat_node->inputs()[i]; Node* cur_input_node = cur_input_value->node(); if (cur_input_node->kind() == kConcat && cur_input_value->uses().size() == 1 && cur_input_node->hasAttribute(kaxis) && cur_input_node->i(kaxis) == concat_node->i(kaxis)) { transform_ran = true; // Inserts n inputs of cur_input_node at index i+1~i+1+(n-1), // and remove cur_input_node at index i. // As a result, cur_input_node is replaced by its inputs inplace, // instead of always appending its inputs at the end. for (size_t j = 0; j < cur_input_node->inputs().size(); j++) { Value* value = cur_input_node->input(j); insertInput(concat_node, i + 1 + j, value); } concat_node->removeInput(i); cur_input_node->destroy(); } } return transform_ran; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/split.h0000664000000000000000000001573113655345213017025 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { static constexpr const char* impure_operators[] = { "RandomNormal", "RandomNormalLike", "RandomUniform", "RandomUniformLike", "Loop", "If", "Scan", }; static bool is_pure_operator(Node* n) { for (auto x : impure_operators) { if (n->kind() == Symbol(x)) { return false; } } return true; } // Split the graph into 'init' and 'predict' nets. This is kind of // like constant folding, except that rather than actually execute the // constant computations, we simply split them out into a separate // graph. Nodes that have any transitive dependency on the // initializers, or on impure operators, must remain in the predict // net. All others may be moved to the init net. // // This function destructively mutates the graph into either the init // or the predict net. If you want both, which you probably do, // arrange to call it twice. // // NOTE POTENTIAL BREAKAGE: // // The ONNX spec provides no guarantees about "staging", i.e. which // inputs change on every invocation vs which generally stay the same. // Here we make the assumption that inputs which have an initializer // value provided for them vary only between invocations of the init // net, and are constant across runs of the predict net. // static void split_init_and_predict(Graph& graph, bool init, bool predict) { // The first step is to identify which Values are reachable from // either of // - inputs without corresponding initializers // - impure operators // Any such Values belong to the predict net. Nodes belong to the // predict net if they are impure or if any of their inputs do. std::unordered_set predict_net_values; auto value_belongs_to_predict_net = [&](Value* v) { return predict_net_values.count(v) > 0; }; auto node_belongs_to_predict_net = [&](Node* n) { return !is_pure_operator(n) || std::any_of( n->inputs().begin(), n->inputs().end(), value_belongs_to_predict_net); }; { std::unordered_set initializer_names( graph.initializer_names().begin(), graph.initializer_names().end()); for (Value* v : graph.inputs()) { if (initializer_names.count(v->uniqueName()) == 0) { predict_net_values.insert(v); } } } for (Node* n : graph.nodes()) { if (node_belongs_to_predict_net(n)) { for (Value* v : n->outputs()) { predict_net_values.insert(v); } } } // Any Value which is not itself in the predict net, but which // is used by a Node which is, becomes an output of the init // graph and an input of the predict net std::unordered_set new_interface; for (Node* n : graph.nodes()) { if (node_belongs_to_predict_net(n)) { for (Value* v : n->inputs()) { if (!value_belongs_to_predict_net(v)) { new_interface.insert(v); } } } } for (Value* v : graph.outputs()) { if (!value_belongs_to_predict_net(v)) { new_interface.insert(v); } } if (init) { // Add new outputs corresponding to the boundary between init and // predict nets, ensuring that we don't duplicate outputs. for (Value* v : graph.outputs()) { new_interface.erase(v); } for (Value* v : new_interface) { if (v->node()->kind() == kUndefined) { continue; } graph.registerOutput(v); } // Remove outputs that belong to the predict net. for (auto i = graph.outputs().size(); i--;) { if (value_belongs_to_predict_net(graph.outputs()[i])) { graph.return_node()->removeInput(i); } } // Delete nodes that belong to the predict net, in reverse // topological order. for (auto it = graph.nodes().rbegin(); it != graph.nodes().rend(); it++) { if (node_belongs_to_predict_net(*it)) { it.destroyCurrent(); } } // Remove inputs that belong to the predict net. for (auto i = graph.inputs().size(); i--;) { if (value_belongs_to_predict_net(graph.inputs()[i])) { graph.eraseInput(i); } } } else if (predict) { // When creating the predict net, 'undefined' nodes will // naturally go into the init net. We need to have a place to // copy the ones we want to keep in the predict net. auto* optionalInputDummyNode = graph.create(kUndefined, 1); graph.appendNode(optionalInputDummyNode); optionalInputDummyNode->outputs()[0]->setUniqueName(""); // Add new inputs, ensuring that we don't introduce duplicates. // Also cut the boundary between init and predict net by replacing // the Values along the boundary with replaceAllUsesWith. for (Value* v : graph.inputs()) { new_interface.erase(v); } for (Value* v : new_interface) { if (v->node()->kind() == kUndefined) { v->replaceAllUsesWith(optionalInputDummyNode->outputs()[0]); } else { Value* newv = graph.addInput()->copyMetadata(v); v->replaceAllUsesWith(newv); } } // Delete nodes that aren't in the predict net, in reverse // topological order. for (auto it = graph.nodes().rbegin(); it != graph.nodes().rend(); it++) { if (*it == optionalInputDummyNode) { continue; } if (node_belongs_to_predict_net(*it)) { continue; } it.destroyCurrent(); } // Remove inputs that aren't used by the predict net. for (auto i = graph.inputs().size(); i--;) { if (graph.inputs()[i]->uses().empty()) { graph.eraseInput(i); } } // Remove all initializers, they are already in the init net. graph.clearInitializers(); } } struct SplitInit final : public FullGraphBasedPass { explicit SplitInit() : FullGraphBasedPass( PassType::Separate, PassEfficiency::Complete, PassOptimizationType::Memory) {} std::string getPassName() const override { return "split_init"; } PassAnalysisType getPassAnalysisType() const override { return PassAnalysisType::Empty; } std::shared_ptr runPass(Graph& graph) override { split_init_and_predict(graph, true, false); return std::shared_ptr(new PostPassAnalysis()); } }; struct SplitPredict final : public FullGraphBasedPass { explicit SplitPredict() : FullGraphBasedPass( PassType::Separate, PassEfficiency::Complete, PassOptimizationType::Memory) {} std::string getPassName() const override { return "split_predict"; } PassAnalysisType getPassAnalysisType() const override { return PassAnalysisType::Empty; } std::shared_ptr runPass(Graph& graph) override { split_init_and_predict(graph, false, true); return std::shared_ptr(new PostPassAnalysis()); } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_consecutive_squeezes.h0000664000000000000000000000515413655345213023165 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // X is a tensor with shape=[1, 1, 2, 3, 1, 5, 1] // Y = Squeeze(X, axes=[1, 4]) -> shape=[1, 2, 3, 5, 1] // Z = Squeeze(Y, axes=[0, 4]) -> shape=[2, 3, 5] // After: // Z = Squeeze(X, axes=[0, 1, 4, 6]) #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseConsecutiveSqueezes final : public PredicateBasedPass { explicit FuseConsecutiveSqueezes() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_consecutive_squeezes"; } // returns a vector `ret` such that squeeze by `ret` is equivalent // to squeeze by `axes_1` and then by `axes_2` std::vector compose_squeezes( const std::vector& axes_1, const std::vector& axes_2) { std::vector ret; ret.reserve(axes_1.size() + axes_2.size()); std::vector sorted_axes_1(axes_1.begin(), axes_1.end()); std::sort(sorted_axes_1.begin(), sorted_axes_1.end()); std::copy( sorted_axes_1.begin(), sorted_axes_1.end(), std::back_inserter(ret)); for (int64_t i : axes_2) { for (auto iter = sorted_axes_1.begin(); iter != sorted_axes_1.end(); ++iter) { // if current axis 1 - prev_num is bigger than axis 2 // put axis 2 + prev_num as new axis int64_t prev_num = std::distance(sorted_axes_1.begin(), iter); if (*iter - prev_num > i) { ret.push_back(i + prev_num); break; } // if no current axis 1 - prev_num is bigger than axis 2 // put axis 2 + prev_num + 1 as new axis if (std::next(iter) == sorted_axes_1.end()) { ret.push_back(i + prev_num + 1); } } } std::sort(ret.begin(), ret.end()); return ret; } bool patternMatchPredicate(Node* node) override { return node->kind() == kSqueeze && node->input()->node()->kind() == kSqueeze; } bool runTransform(Node* n, Graph&, NodeDestroyType& destroy_current) override { auto orig_input = n->input(); n->is_( kaxes, compose_squeezes(orig_input->node()->is(kaxes), n->is(kaxes))); n->replaceInput(0, orig_input->node()->input()); if (orig_input->uses().size() == 0) { orig_input->node()->destroy(); } destroy_current = NodeDestroyType::DestroyZero; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_nop_dropout.h0000664000000000000000000000265513655345213022272 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct EliminateNopDropout final : public PredicateBasedPass { explicit EliminateNopDropout() : PredicateBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "eliminate_nop_dropout"; } bool patternMatchPredicate(Node* node) override { // in opset 12, ratio is an input of Dropout rather than an attribute, // however we don't want to to remove Dropout fro opset 12+, since it // supports training-friendly models, for which the Dropout ops are required return (node->kind() == kDropout && node->hasAttribute(kratio)) && node->f(kratio) == 0.0; } bool runTransform(Node* node, Graph&, NodeDestroyType& destroy_current) override { // Don't assume that theres only one output. for (size_t i = 0; i < node->outputs().size(); ++i) { node->outputs()[i]->replaceAllUsesWith(node->input()); } if (node->outputs()[0]->has_sizes()) { node->input()->setSizes(node->outputs()[0]->sizes()); } destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_pad_into_conv.h0000664000000000000000000001264513655345213021537 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once // Before: // P = Pad(X) - opset 10 and below (or) Pad(X, Pads, [Constant_value]) - opset 11 and // above Z = Conv(P, Y) // After: // Z = Conv(X, Y) with "pads" attribute set // // the pass handles the case when Pad is zero-padding the input // (i.e. mode=constant and Constant_value=0) #include #include "onnx/defs/tensor_util.h" #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FusePadIntoConv final : public PredicateBasedPass { explicit FusePadIntoConv() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_pad_into_conv"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kConv && node->inputs()[0]->node()->kind() == kPad; } bool runTransform(Node* n, Graph& graph, NodeDestroyType& destroy_current) override { destroy_current = NodeDestroyType::DestroyZero; // check if Pad is only used by Conv if (n->inputs()[0]->uses().size() > 1) { return false; } Node* conv = n; Node* pad = n->inputs()[0]->node(); // Process 'pads' data std::vector pads; if (pad->hasAttribute(kpads)) { // opset 10 and below pads = pad->is(kpads); } else { // opset 11 and above - first check if 'pad' node has 'pads' input // initialized const auto& pads_name = pad->inputs()[1]->uniqueName(); const auto pads_initializer = graph.getInitializer(pads_name); // 'pad' node has the 'pads' input which has not been initialized - // can't proceed with fusing if (pads_initializer == graph.initializers().end()) { return false; } // make sure the type of 'pads' is INT64 if (pads_initializer->elem_type() != TensorProto::INT64) { return false; } // parse 'pads' data from the initialized input pads = ParseData(&*pads_initializer); } // Process 'mode' std::string pad_mode; if (pad->hasAttribute(kmode)) { pad_mode = pad->s(kmode); } else { pad_mode = "constant"; } // cannot fuse if the pad mode is not "Constant" if (pad_mode != "constant") { return false; } // Process 'Constant_value' // opset 10 and below if (pad->hasAttribute(kvalue) && static_cast(pad->f(kvalue)) != 0.0) { return false; } else if (pad->inputs().size() == 3) { // opset 11 and above - check if the 'pad' node has the optional 'Constant_value' // input check if it has data initialized const auto& value_name = pad->inputs()[2]->uniqueName(); const auto value_initializer = graph.getInitializer(value_name); // 'pad' node has the 'Constant_value' input which has not been initialized - // can't proceed with fusing if (value_initializer == graph.initializers().end()) { return false; } // parse 'Constant_value' data from the initialized input and stop optimizer if the // Constant_value is non-zero switch (value_initializer->elem_type()) { case TensorProto::FLOAT: if (ParseData(&*value_initializer)[0] != 0) return false; // cannot fuse Pad into Conv else break; case TensorProto::DOUBLE: if (ParseData(&*value_initializer)[0] != 0) return false; // cannot fuse Pad into Conv else break; case TensorProto::INT32: if (ParseData(&*value_initializer)[0] != 0) return false; // cannot fuse Pad into Conv else break; case TensorProto::INT64: if (ParseData(&*value_initializer)[0] != 0) return false; // cannot fuse Pad into Conv else break; // TODO: Support more uncommon but valid types for Pad op (int8, uint8, int16, uint16, etc.) default: return false; // Either type of Constant_value is invalid or not yet supported by data parsing logic. // Since we canot validate the data present in 'Constant_value', we exit the optimizer } } // check if some values in 'pads' prevents us from fusing it into 'Conv' node int pads_size = static_cast(pads.size()); // check if padding is applied only on feature dims if (pads[0] != 0 || pads[1] != 0 || pads[pads_size / 2] != 0 || pads[pads_size / 2 + 1] != 0) { return false; } // check if padding is only positive if (std::any_of(pads.begin(), pads.end(), [](int64_t local_value) { return local_value < 0; })) { return false; } int conv_pads_size = pads_size - 4; std::vector conv_pads(conv_pads_size, 0); // Fuse into existing padding, if available if (conv->hasAttribute(kpads)) { conv_pads = conv->is(kpads); } for (int i = 2, j = 0; i < pads_size / 2; ++i, ++j) { conv_pads[j] += pads[i]; conv_pads[conv_pads_size / 2 + j] += pads[pads_size / 2 + i]; } conv->is_(kpads, std::move(conv_pads)); conv->replaceInput(0, pad->inputs()[0]); pad->destroy(); return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/fuse_transpose_into_gemm.h0000664000000000000000000000260313655345213022762 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct FuseTransposeIntoGemm final : public PredicateBasedPass { explicit FuseTransposeIntoGemm() : PredicateBasedPass( PassType::Fuse, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "fuse_transpose_into_gemm"; } bool patternMatchPredicate(Node* node) override { return node->kind() == kGemm; } bool runTransform(Node* n, Graph&, NodeDestroyType& destroy_current) override { const std::vector simple_trans_perm({1, 0}); destroy_current = NodeDestroyType::DestroyZero; bool ret_val = false; for (size_t i : {0, 1}) { auto inp = n->inputs()[i]; auto trans = i == 0 ? ktransA : ktransB; if (inp->node()->kind() == kTranspose && inp->node()->is(kperm) == simple_trans_perm) { n->replaceInput(i, inp->node()->input()); n->i_(trans, n->hasAttribute(trans) ? !n->i(trans) : 1); if (inp->uses().size() == 0) { inp->node()->destroy(); ret_val = true; } } } return ret_val; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/passes/eliminate_nop_transpose.h0000664000000000000000000000242313655345213022605 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/optimizer/pass.h" namespace ONNX_NAMESPACE { namespace optimization { struct EliminateNopTranspose final : public PredicateBasedPass { explicit EliminateNopTranspose() : PredicateBasedPass( PassType::Nop, PassEfficiency::Complete, PassOptimizationType::Compute) {} std::string getPassName() const override { return "eliminate_nop_transpose"; } static bool is_nop_transpose(const std::vector& perm) { for (size_t i = 0; i < perm.size(); i++) if (perm[i] != (int)i) return false; return true; } bool patternMatchPredicate(Node* node) override { return (node->kind() == kTranspose && node->hasAttribute(kperm)) && is_nop_transpose(node->is(kperm)); } bool runTransform(Node* node, Graph&, NodeDestroyType& destroy_current) override { if (node->output()->has_sizes()) { node->input()->setSizes(node->output()->sizes()); } node->output()->replaceAllUsesWith(node->input()); destroy_current = NodeDestroyType::DestroyOne; return true; } }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/pass_registry.cc0000664000000000000000000000075213655345213017425 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #include "onnx/optimizer/pass_registry.h" namespace ONNX_NAMESPACE { namespace optimization { const std::vector GlobalPassRegistry::GetAvailablePasses() { std::vector names; for (const auto& pass : this->passes) { names.push_back(pass.first); } return names; } } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/pass.cc0000664000000000000000000000556113655345213015500 0ustar rootroot#include "onnx/optimizer/pass.h" #include "onnx/common/assertions.h" namespace ONNX_NAMESPACE { namespace optimization { Pass::Pass( PassType pass_type, PassEfficiency pass_efficiency, PassOptimizationType pass_optimization_type) { this->pass_type = pass_type; this->pass_efficiency = pass_efficiency; this->pass_optimization_type = pass_optimization_type; } Pass::~Pass() {} unsigned int Pass::DescendOnGraphAttributesAndCount( Node* n, std::function fn) { unsigned int num_changes = 0; for (auto name : n->attributeNames()) { auto kind = n->kindOf(name); if (kind == AttributeKind::g) { num_changes += fn(*n->g(name)); } if (kind == AttributeKind::gs) { for (auto& g : n->gs(name)) { num_changes += fn(*g); } } } return num_changes; } void Pass::DescendOnGraphAttributesUnconstrained( Node* n, std::function fn) { for (auto name : n->attributeNames()) { auto kind = n->kindOf(name); if (kind == AttributeKind::g) { fn(*n->g(name)); } if (kind == AttributeKind::gs) { for (auto& g : n->gs(name)) { fn(*g); } } } } PredicateBasedPass::~PredicateBasedPass() {} unsigned int PredicateBasedPass::_runPassInternal(Graph& graph) { unsigned int num_changes = false; for (auto it = graph.begin(); it != graph.end(); ++it) { auto* n = *it; num_changes += this->DescendOnGraphAttributesAndCount( n, [this](Graph& g) { return _runPassInternal(g); }); if (this->patternMatchPredicate(n)) { NodeDestroyType destroy_type = NodeDestroyType::DestroyZero; num_changes += this->runTransform(n, graph, destroy_type); if (destroy_type == NodeDestroyType::DestroyOne) { it.destroyCurrent(); } if (destroy_type == NodeDestroyType::DestroyTwo) { it.destroyCurrent(); it.destroyCurrent(); } } } return num_changes; } PassAnalysisType PredicateBasedPass::getPassAnalysisType() const { return PassAnalysisType::CountBased; } std::shared_ptr PredicateBasedPass::runPass(Graph& graph) { bool initialized_pass = this->initializePass(graph); unsigned int touched_optimizations = this->_runPassInternal(graph); bool finalized_pass = this->finalizePass(graph); return std::shared_ptr(new CountBasedPassAnalysis( this, touched_optimizations, initialized_pass, finalized_pass)); } CountBasedPassAnalysis::CountBasedPassAnalysis( Pass* pass, unsigned int num_positive_transforms, bool initialization_done, bool finalization_done) { this->pass = pass; this->num_positive_transforms = num_positive_transforms; this->initialization_done = initialization_done; this->finalization_done = finalization_done; } FullGraphBasedPass::~FullGraphBasedPass() {} } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/pass_manager.cc0000664000000000000000000000301413655345213017161 0ustar rootroot#include "onnx/optimizer/pass_manager.h" namespace ONNX_NAMESPACE { namespace optimization { PassManager::PassManager() {} PassManager::~PassManager() {} GeneralPassManager::~GeneralPassManager() { this->passes.clear(); } void GeneralPassManager::add(std::shared_ptr pass) { this->passes.push_back(std::move(pass)); } std::shared_ptr GeneralPassManager::run(Graph& graph) { for (const std::shared_ptr& pass : this->passes) { auto pass_analysis = pass->runPass(graph); } return std::shared_ptr(new EmptyPassManagerAnalysis()); } std::shared_ptr FixedPointPassManager::run(Graph& graph) { bool fixed_point_optimization_done; do { fixed_point_optimization_done = false; for (const std::shared_ptr& pass : this->passes) { std::shared_ptr analysis = pass->runPass(graph); if (pass->getPassAnalysisType() == PassAnalysisType::Empty) { continue; } std::shared_ptr count_analysis = std::static_pointer_cast(analysis); while (count_analysis->fixedPointOptimizationNeeded()) { count_analysis = std::static_pointer_cast( pass->runPass(graph)); fixed_point_optimization_done = true; } } } while (fixed_point_optimization_done); return std::shared_ptr(new EmptyPassManagerAnalysis()); } } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/pass_manager.h0000664000000000000000000000317313655345213017031 0ustar rootroot#pragma once // ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #include #include "onnx/optimizer/pass.h" #include "onnx/optimizer/passes/eliminate_deadend.h" namespace ONNX_NAMESPACE { namespace optimization { // An analysis returned from the run done by a manager struct PassManagerAnalysis {}; struct EmptyPassManagerAnalysis : PassManagerAnalysis {}; // Base class of all PassManager's. The class should be able to add new passes // as well as run the passes given a graph. class PassManager { public: PassManager(); virtual ~PassManager(); virtual void add(std::shared_ptr P) = 0; virtual std::shared_ptr run(Graph& graph) = 0; }; // The GeneralPassManager has no restriction on type of Pass and runs the passes // once in a linear fashion. class GeneralPassManager : public PassManager { public: GeneralPassManager() {} ~GeneralPassManager() override; void add(std::shared_ptr pass) override; std::shared_ptr run(Graph& graph) override; protected: // use vector here to ensure the order of the passes // for some pass, order is critical, for example, // split_init and split_predict should be the last in the list std::vector> passes; }; // Exhibits the same behavior as GeneralPassManager but will instead check // whether or not fixed point optimization is needed. class FixedPointPassManager : public GeneralPassManager { std::shared_ptr run(Graph& graph) override; }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/pass.h0000664000000000000000000001546413655345213015345 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include #include "onnx/common/ir.h" #include "onnx/onnx_pb.h" namespace ONNX_NAMESPACE { namespace optimization { // Base struct representing result of a pass. struct PostPassAnalysis { virtual ~PostPassAnalysis() = default; }; // Enum that represents the type of optimization it is. enum PassType { // Class of optimizations that fuses operations. Fuse = 0, // Class of optimizations that removes useless operations. Nop = 1, // Class of optimizations that includes some form of seperation. Separate = 2, // Immutable pass, also sometimes referred to as an analysis pass. Immutable = 3, // Other type of pass. Other = 4 }; // Enum that represents the return type of the analysis. enum PassAnalysisType { // An empty analysis is returned. Most likely will return PostPassAnalysis. Empty = 0, // A count based analysis is returned. Most likely of type // CountBasedPassAnalysis CountBased = 1 }; enum PassEfficiency { // A partially efficient optimization pass cannot guarantee that running two // consecutive passes // will return the same result as running a single pass. Partial = 0, // A completely efficient optimization guarantees that running two consecutive // passes is equivalent // to running a single pass. Complete = 1 }; // Describes what the optimization pass is attempting to optimize. enum PassOptimizationType { // Is not optimizing anything. Most likely will be used in an immutable pass. None = 0, // Optimizes for compute. Compute = 1, // Optimizes for memory. Memory = 2, // Optimizes for both compute and memory. ComputeMemory = 3, // Optimizes for stability (e.g. log-sum-exp trick). Stability = 4 }; enum NodeDestroyType { // Does not destroy node DestroyZero = 0, // Equivalent to calling it.destroyCurrent() once. DestroyOne = 1, // Equivalent to calling it.destroyCurrent() twice. DestroyTwo = 2 }; // Base class for all optimizations within ONNX. A pass must contain the // annotations described above. Furthermore each pass is given the ability to // initialize and finalize it's pass. Each pass must have a unique name that // pass managers/registry will use as identification. Finally the pass // implements runPass which completes the pass inplace. class Pass { PassType pass_type; PassEfficiency pass_efficiency; PassOptimizationType pass_optimization_type; public: Pass( PassType pass_type, PassEfficiency pass_efficiency, PassOptimizationType pass_optimization_type); virtual ~Pass(); PassType getPassType() const { return this->pass_type; } PassEfficiency getPassEfficiency() const { return this->pass_efficiency; } PassOptimizationType getPassOptimizationType() const { return this->pass_optimization_type; } virtual PassAnalysisType getPassAnalysisType() const = 0; virtual std::string getPassName() const = 0; virtual bool initializePass(Graph&) { return false; } virtual bool finalizePass(Graph&) { return false; } virtual std::shared_ptr runPass(Graph& graph) = 0; protected: // Iterates through the elements in the graph and counts the number of times // the transform is successfully run. unsigned int DescendOnGraphAttributesAndCount( Node* n, std::function fn); // A more general version of the function above that doesn't constrain the // return type of fn. void DescendOnGraphAttributesUnconstrained( Node* n, std::function fn); }; class ImmutablePass : Pass { public: explicit ImmutablePass() : Pass( PassType::Immutable, PassEfficiency::Complete, PassOptimizationType::None) {} ~ImmutablePass() override; }; // Pass Analysis done after a predicate based pass. struct CountBasedPassAnalysis : PostPassAnalysis { // Have to use raw pointer here. The idea is that the pass will pass as // a parameter to the constructor. We could use std::enable_shared_from_this // but this complicates the memory model. Also since all passes come from // GlobalPassRegistry which already utilizes smart pointers we don't have to // worry about memory leaks from passes. Pass* pass; unsigned int num_positive_transforms; bool initialization_done; bool finalization_done; public: explicit CountBasedPassAnalysis( Pass* pass, unsigned int num_positive_transforms, bool initialization_done, bool finalization_done); bool graphChanged() { return this->num_positive_transforms > 0; } bool numSucceededTransforms() { return this->num_positive_transforms; } // Whether or not a repeated application of the pass might be useful. bool fixedPointOptimizationNeeded() { return this->graphChanged() && pass->getPassEfficiency() == PassEfficiency::Partial; } }; // A pass that is based on pattern matching. The majority of passes will // implement this pass. In order for the pass to work the patternMatchPredicate // function must be implemented witch matches a subgraph to the respective // optimization pass. Lastly the runTransform method must also be implemented // which simply implements the pass on any node which passes // patternMatchPredicate. class PredicateBasedPass : public Pass { public: explicit PredicateBasedPass( PassType pass_type, PassEfficiency pass_efficiency, PassOptimizationType pass_optimization_type) : Pass(pass_type, pass_efficiency, pass_optimization_type) {} ~PredicateBasedPass() override; virtual bool patternMatchPredicate(Node* node) = 0; // Run transform is given the current node in the iterator, a reference to the // current graph as well as a reference describing how to treat the current // node in the iterator post transform. Run transform is then responsible for // running the actual transform as well as describing how to treat the // iterator node. By default the current node will not call destroy. Do not // internally delete node instead set the correct destroy_current type. virtual bool runTransform(Node* node, Graph& graph, NodeDestroyType& destroy_current) = 0; std::shared_ptr runPass(Graph& graph) override; PassAnalysisType getPassAnalysisType() const override; private: unsigned int _runPassInternal(Graph& graph); }; // The most general pass which allows the user to run a pass given only a graph. class FullGraphBasedPass : public Pass { public: explicit FullGraphBasedPass( PassType pass_type, PassEfficiency pass_efficiency, PassOptimizationType pass_optimization_type) : Pass(pass_type, pass_efficiency, pass_optimization_type) {} ~FullGraphBasedPass() override; }; } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/optimizer/optimize.cc0000664000000000000000000000233413655345213016365 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #include "onnx/optimizer/optimize.h" namespace ONNX_NAMESPACE { namespace optimization { GlobalPassRegistry Optimizer::passes; Optimizer::Optimizer( const std::vector& names, const bool fixed_point) { if (fixed_point) { this->pass_manager = std::shared_ptr(new FixedPointPassManager()); } else { this->pass_manager = std::shared_ptr(new GeneralPassManager()); } for (const auto& name : names) { auto pass = passes.find(name); this->pass_manager->add(pass); } } Optimizer::~Optimizer() {} ModelProto Optimize( const ModelProto& mp_in, const std::vector& names) { Optimizer current_opt(names, false); return current_opt.optimize(mp_in); } ModelProto OptimizeFixed( const ModelProto& mp_in, const std::vector& names) { Optimizer current_opt(names, true); return current_opt.optimize(mp_in); } const std::vector GetAvailablePasses() { return Optimizer::passes.GetAvailablePasses(); } } // namespace optimization } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/py_utils.h0000664000000000000000000000071213655345213014213 0ustar rootroot#pragma once #include #include "onnx/proto_utils.h" namespace ONNX_NAMESPACE { namespace py = pybind11; template bool ParseProtoFromPyBytes(Proto* proto, const py::bytes& bytes) { // Get the buffer from Python bytes object char* buffer = nullptr; Py_ssize_t length; PyBytes_AsStringAndSize(bytes.ptr(), &buffer, &length); return ParseProtoFromBytes(proto, buffer, length); } } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/examples/0000775000000000000000000000000013655345213014010 5ustar rootrootonnx-1.7.0/onnx/examples/make_model.ipynb0000664000000000000000000001030213655345213017144 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The ir_version in model: 7\n", "\n", "The producer_name in model: onnx-example\n", "\n", "The graph in model:\n", "node {\n", " input: \"X\"\n", " input: \"Pads\"\n", " output: \"Y\"\n", " op_type: \"Pad\"\n", " attribute {\n", " name: \"mode\"\n", " s: \"constant\"\n", " type: STRING\n", " }\n", "}\n", "name: \"test-model\"\n", "initializer {\n", " dims: 4\n", " data_type: 7\n", " int64_data: 0\n", " int64_data: 0\n", " int64_data: 1\n", " int64_data: 1\n", " name: \"Pads\"\n", "}\n", "input {\n", " name: \"X\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " dim {\n", " dim_value: 2\n", " }\n", " }\n", " }\n", " }\n", "}\n", "input {\n", " name: \"Pads\"\n", " type {\n", " tensor_type {\n", " elem_type: 7\n", " shape {\n", " dim {\n", " dim_value: 4\n", " }\n", " }\n", " }\n", " }\n", "}\n", "output {\n", " name: \"Y\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " dim {\n", " dim_value: 4\n", " }\n", " }\n", " }\n", " }\n", "}\n", "\n", "The model is checked!\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import onnx\n", "from onnx import helper\n", "from onnx import AttributeProto, TensorProto, GraphProto\n", "\n", "\n", "# The protobuf definition can be found here:\n", "# https://github.com/onnx/onnx/blob/master/onnx/onnx.proto\n", "\n", "\n", "# Create one input (ValueInfoProto)\n", "X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 2])\n", "\n", "# Create second input (ValueInfoProto)\n", "Pads = helper.make_tensor_value_info('Pads', TensorProto.INT64, [4])\n", "\n", "# Create one output (ValueInfoProto)\n", "Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 4])\n", "\n", "# Create a node (NodeProto)\n", "node_def = helper.make_node(\n", " 'Pad', # node name\n", " ['X', 'Pads'], # inputs\n", " ['Y'], # outputs\n", " mode='constant', # Attributes\n", ")\n", "\n", "# Create the graph (GraphProto)\n", "graph_def = helper.make_graph(\n", " [node_def],\n", " \"test-model\",\n", " [X, Pads],\n", " [Y],\n", " [helper.make_tensor('Pads', TensorProto.INT64, [4,], [0, 0, 1, 1,])],\n", ")\n", "\n", "# Create the model (ModelProto)\n", "model_def = helper.make_model(graph_def,\n", " producer_name='onnx-example')\n", "\n", "print('The ir_version in model: {}\\n'.format(model_def.ir_version))\n", "print('The producer_name in model: {}\\n'.format(model_def.producer_name))\n", "print('The graph in model:\\n{}'.format(model_def.graph))\n", "onnx.checker.check_model(model_def)\n", "print('The model is checked!')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/examples/optimize_onnx.ipynb0000664000000000000000000000672213655345213017764 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The model before optimization:\n", "\n", "graph two-transposes (\n", " %X[FLOAT, 2x3x4]\n", ") {\n", " %Y = Transpose[perm = [1, 0, 2]](%X)\n", " %Z = Transpose[perm = [1, 0, 2]](%Y)\n", " return %Z\n", "}\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import onnx\n", "import os\n", "from onnx import optimizer\n", "\n", "# Preprocessing: load the model contains two transposes.\n", "model_path = os.path.join('resources', 'two_transposes.onnx')\n", "original_model = onnx.load(model_path)\n", "\n", "print('The model before optimization:\\n\\n{}'.format(onnx.helper.printable_graph(original_model.graph)))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Available optimization passes:\n", "\teliminate_deadend\n", "\teliminate_identity\n", "\teliminate_nop_dropout\n", "\teliminate_nop_monotone_argmax\n", "\teliminate_nop_pad\n", "\teliminate_nop_transpose\n", "\teliminate_unused_initializer\n", "\textract_constant_to_initializer\n", "\tfuse_add_bias_into_conv\n", "\tfuse_bn_into_conv\n", "\tfuse_consecutive_concats\n", "\tfuse_consecutive_log_softmax\n", "\tfuse_consecutive_reduce_unsqueeze\n", "\tfuse_consecutive_squeezes\n", "\tfuse_consecutive_transposes\n", "\tfuse_matmul_add_bias_into_gemm\n", "\tfuse_pad_into_conv\n", "\tfuse_transpose_into_gemm\n", "\tlift_lexical_references\n", "\tnop\n", "\tsplit_init\n", "\tsplit_predict\n", "\n", "The model after optimization:\n", "\n", "graph two-transposes (\n", " %X[FLOAT, 2x3x4]\n", ") {\n", " %Z = Transpose[perm = [0, 1, 2]](%X)\n", " return %Z\n", "}\n" ] } ], "source": [ "# A full list of supported optimization passes can be found using get_available_passes()\n", "all_passes = optimizer.get_available_passes()\n", "print(\"Available optimization passes:\")\n", "for p in all_passes:\n", " print('\\t{}'.format(p))\n", "print()\n", "\n", "# Pick one pass as example\n", "passes = ['fuse_consecutive_transposes']\n", "\n", "# Apply the optimization on the original serialized model\n", "optimized_model = optimizer.optimize(original_model, passes)\n", "\n", "print('The model after optimization:\\n\\n{}'.format(onnx.helper.printable_graph(optimized_model.graph)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.1" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/examples/check_model.ipynb0000664000000000000000000000515413655345213017315 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The model is:\n", "ir_version: 3\n", "producer_name: \"backend-test\"\n", "graph {\n", " node {\n", " input: \"x\"\n", " output: \"y\"\n", " name: \"test\"\n", " op_type: \"Relu\"\n", " }\n", " name: \"SingleRelu\"\n", " input {\n", " name: \"x\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " dim {\n", " dim_value: 2\n", " }\n", " }\n", " }\n", " }\n", " }\n", " output {\n", " name: \"y\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " dim {\n", " dim_value: 2\n", " }\n", " }\n", " }\n", " }\n", " }\n", "}\n", "opset_import {\n", " version: 6\n", "}\n", "\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import onnx\n", "import os\n", "\n", "\n", "# Preprocessing: load the ONNX model\n", "model_path = os.path.join('resources', 'single_relu.onnx')\n", "onnx_model = onnx.load(model_path)\n", "\n", "print('The model is:\\n{}'.format(onnx_model))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The model is checked!\n" ] } ], "source": [ "# Check the model\n", "onnx.checker.check_model(onnx_model)\n", "print('The model is checked!')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/examples/shape_inference.ipynb0000664000000000000000000000547713655345213020206 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Before shape inference, the shape info of Y is:\n", "[]\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import onnx\n", "from onnx import helper, shape_inference\n", "from onnx import TensorProto\n", "\n", "\n", "# Preprocessing: create a model with two nodes, Y's shape is unknown\n", "node1 = helper.make_node('Transpose', ['X'], ['Y'], perm=[1, 0, 2])\n", "node2 = helper.make_node('Transpose', ['Y'], ['Z'], perm=[1, 0, 2])\n", "\n", "graph = helper.make_graph(\n", " [node1, node2],\n", " 'two-transposes',\n", " [helper.make_tensor_value_info('X', TensorProto.FLOAT, (2, 3, 4))],\n", " [helper.make_tensor_value_info('Z', TensorProto.FLOAT, (2, 3, 4))],\n", ")\n", "\n", "original_model = helper.make_model(graph, producer_name='onnx-examples')\n", "\n", "# Check the model and print Y's shape information\n", "onnx.checker.check_model(original_model)\n", "print('Before shape inference, the shape info of Y is:\\n{}'.format(original_model.graph.value_info))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "After shape inference, the shape info of Y is:\n", "[name: \"Y\"\n", "type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 3\n", " }\n", " dim {\n", " dim_value: 2\n", " }\n", " dim {\n", " dim_value: 4\n", " }\n", " }\n", " }\n", "}\n", "]\n" ] } ], "source": [ "# Apply shape inference on the model\n", "inferred_model = shape_inference.infer_shapes(original_model)\n", "\n", "# Check the model and print Y's shape information\n", "onnx.checker.check_model(inferred_model)\n", "print('After shape inference, the shape info of Y is:\\n{}'.format(inferred_model.graph.value_info))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/examples/Protobufs.ipynb0000664000000000000000000003131513655345213017041 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from onnx import *" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Int attribute:\n", "\n", "name: \"this_is_an_int\"\n", "i: 1701\n", "type: INT\n", "\n" ] } ], "source": [ "# Int Attibute\n", "arg = helper.make_attribute(\"this_is_an_int\", 1701)\n", "print(\"\\nInt attribute:\\n\")\n", "print(arg)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Float attribute:\n", "\n", "name: \"this_is_a_float\"\n", "f: 3.1400001049\n", "type: 1\n", "\n" ] } ], "source": [ "#NBVAL_IGNORE_OUTPUT\n", "# Float Attribute\n", "arg = helper.make_attribute(\"this_is_a_float\", 3.14)\n", "print(\"\\nFloat attribute:\\n\")\n", "print(arg)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "String attribute:\n", "\n", "name: \"this_is_a_string\"\n", "s: \"string_content\"\n", "type: STRING\n", "\n" ] } ], "source": [ "# String Attribute\n", "arg = helper.make_attribute(\"this_is_a_string\", \"string_content\")\n", "print(\"\\nString attribute:\\n\")\n", "print(arg)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Repeated int attribute:\n", "\n", "name: \"this_is_a_repeated_int\"\n", "ints: 1\n", "ints: 2\n", "ints: 3\n", "ints: 4\n", "type: INTS\n", "\n" ] } ], "source": [ "# Repeated Attribute\n", "arg = helper.make_attribute(\"this_is_a_repeated_int\", [1, 2, 3, 4])\n", "print(\"\\nRepeated int attribute:\\n\")\n", "print(arg)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "NodeProto:\n", "\n", "input: \"X\"\n", "output: \"Y\"\n", "op_type: \"Relu\"\n", "\n" ] } ], "source": [ "# node\n", "node_proto = helper.make_node(\"Relu\", [\"X\"], [\"Y\"])\n", "\n", "print(\"\\nNodeProto:\\n\")\n", "print(node_proto)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "NodeProto:\n", "\n", "input: \"X\"\n", "input: \"W\"\n", "input: \"B\"\n", "output: \"Y\"\n", "op_type: \"Conv\"\n", "attribute {\n", " name: \"kernel\"\n", " i: 3\n", " type: INT\n", "}\n", "attribute {\n", " name: \"pad\"\n", " i: 1\n", " type: INT\n", "}\n", "attribute {\n", " name: \"stride\"\n", " i: 1\n", " type: INT\n", "}\n", "\n", "\n", "More Readable NodeProto (no args yet):\n", "\n", "%Y = Conv[kernel = 3, pad = 1, stride = 1](%X, %W, %B)\n" ] } ], "source": [ "# node with args\n", "node_proto = helper.make_node(\n", " \"Conv\", [\"X\", \"W\", \"B\"], [\"Y\"],\n", " kernel=3, stride=1, pad=1)\n", "\n", "# This is just for making the attributes to be printed in order\n", "node_proto.attribute.sort(key=lambda attr: attr.name)\n", "print(\"\\nNodeProto:\\n\")\n", "print(node_proto)\n", "\n", "print(\"\\nMore Readable NodeProto (no args yet):\\n\")\n", "print(helper.printable_node(node_proto))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "graph proto:\n", "\n", "node {\n", " input: \"X\"\n", " input: \"W1\"\n", " input: \"B1\"\n", " output: \"H1\"\n", " op_type: \"FC\"\n", "}\n", "node {\n", " input: \"H1\"\n", " output: \"R1\"\n", " op_type: \"Relu\"\n", "}\n", "node {\n", " input: \"R1\"\n", " input: \"W2\"\n", " input: \"B2\"\n", " output: \"Y\"\n", " op_type: \"FC\"\n", "}\n", "name: \"MLP\"\n", "input {\n", " name: \"X\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", "}\n", "input {\n", " name: \"W1\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", "}\n", "input {\n", " name: \"B1\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", "}\n", "input {\n", " name: \"W2\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", "}\n", "input {\n", " name: \"B2\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", "}\n", "output {\n", " name: \"Y\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", "}\n", "\n", "\n", "More Readable GraphProto:\n", "\n", "graph MLP (\n", " %X[FLOAT, 1]\n", " %W1[FLOAT, 1]\n", " %B1[FLOAT, 1]\n", " %W2[FLOAT, 1]\n", " %B2[FLOAT, 1]\n", ") {\n", " %H1 = FC(%X, %W1, %B1)\n", " %R1 = Relu(%H1)\n", " %Y = FC(%R1, %W2, %B2)\n", " return %Y\n", "}\n" ] } ], "source": [ "# graph\n", "graph_proto = helper.make_graph(\n", " [\n", " helper.make_node(\"FC\", [\"X\", \"W1\", \"B1\"], [\"H1\"]),\n", " helper.make_node(\"Relu\", [\"H1\"], [\"R1\"]),\n", " helper.make_node(\"FC\", [\"R1\", \"W2\", \"B2\"], [\"Y\"]),\n", " ],\n", " \"MLP\",\n", " [\n", " helper.make_tensor_value_info('X' , TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('W1', TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('B1', TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('W2', TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('B2', TensorProto.FLOAT, [1]),\n", " ],\n", " [\n", " helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1]),\n", " ]\n", ")\n", "\n", "print(\"\\ngraph proto:\\n\")\n", "print(graph_proto)\n", "\n", "print(\"\\nMore Readable GraphProto:\\n\")\n", "print(helper.printable_graph(graph_proto))" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "NodeProto that contains a graph:\n", "\n", "input: \"Input\"\n", "input: \"W1\"\n", "input: \"B1\"\n", "input: \"W2\"\n", "input: \"B2\"\n", "output: \"Output\"\n", "op_type: \"graph\"\n", "attribute {\n", " name: \"graph\"\n", " graphs {\n", " node {\n", " input: \"X\"\n", " input: \"W1\"\n", " input: \"B1\"\n", " output: \"H1\"\n", " op_type: \"FC\"\n", " }\n", " node {\n", " input: \"H1\"\n", " output: \"R1\"\n", " op_type: \"Relu\"\n", " }\n", " node {\n", " input: \"R1\"\n", " input: \"W2\"\n", " input: \"B2\"\n", " output: \"Y\"\n", " op_type: \"FC\"\n", " }\n", " name: \"MLP\"\n", " input {\n", " name: \"X\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", " input {\n", " name: \"W1\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", " input {\n", " name: \"B1\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", " input {\n", " name: \"W2\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", " input {\n", " name: \"B2\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", " output {\n", " name: \"Y\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " }\n", " }\n", " }\n", " }\n", " }\n", " type: GRAPHS\n", "}\n", "\n" ] } ], "source": [ "# An node that is also a graph\n", "graph_proto = helper.make_graph(\n", " [\n", " helper.make_node(\"FC\", [\"X\", \"W1\", \"B1\"], [\"H1\"]),\n", " helper.make_node(\"Relu\", [\"H1\"], [\"R1\"]),\n", " helper.make_node(\"FC\", [\"R1\", \"W2\", \"B2\"], [\"Y\"]),\n", " ],\n", " \"MLP\",\n", " [\n", " helper.make_tensor_value_info('X' , TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('W1', TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('B1', TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('W2', TensorProto.FLOAT, [1]),\n", " helper.make_tensor_value_info('B2', TensorProto.FLOAT, [1]),\n", " ],\n", " [\n", " helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1]),\n", " ]\n", ")\n", "\n", "# output = ThisSpecificgraph([input, w1, b1, w2, b2])\n", "node_proto = helper.make_node(\n", " \"graph\",\n", " [\"Input\", \"W1\", \"B1\", \"W2\", \"B2\"],\n", " [\"Output\"],\n", " graph=[graph_proto],\n", ")\n", "\n", "print(\"\\nNodeProto that contains a graph:\\n\")\n", "print(node_proto)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.11" } }, "nbformat": 4, "nbformat_minor": 1 } onnx-1.7.0/onnx/examples/resources/0000775000000000000000000000000013655345213016022 5ustar rootrootonnx-1.7.0/onnx/examples/resources/single_relu_new.onnx0000664000000000000000000000014013655345213022102 0ustar rootroot backend-test:J  xytest"Relu SingleReluZ x   b y   Bonnx-1.7.0/onnx/examples/resources/two_transposes.onnx0000664000000000000000000000024213655345213022016 0ustar rootroot onnx-examples:Š " XY" Transpose* perm@@@  " YZ" Transpose* perm@@@ two-transposesZ X    b Z    Bonnx-1.7.0/onnx/examples/resources/tensor.pb0000664000000000000000000000007013655345213017654 0ustar rootroot J0ð?@@@@@onnx-1.7.0/onnx/examples/resources/single_relu.onnx0000664000000000000000000000014013655345213021231 0ustar rootroot backend-test:J  xytest"Relu SingleReluZ x   b y   Bonnx-1.7.0/onnx/examples/load_model.ipynb0000664000000000000000000000424013655345213017152 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ir_version: 3\n", "producer_name: \"backend-test\"\n", "graph {\n", " node {\n", " input: \"x\"\n", " output: \"y\"\n", " name: \"test\"\n", " op_type: \"Relu\"\n", " }\n", " name: \"SingleRelu\"\n", " input {\n", " name: \"x\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " dim {\n", " dim_value: 2\n", " }\n", " }\n", " }\n", " }\n", " }\n", " output {\n", " name: \"y\"\n", " type {\n", " tensor_type {\n", " elem_type: 1\n", " shape {\n", " dim {\n", " dim_value: 1\n", " }\n", " dim {\n", " dim_value: 2\n", " }\n", " }\n", " }\n", " }\n", " }\n", "}\n", "opset_import {\n", " version: 6\n", "}\n", "\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import onnx\n", "import os\n", "\n", "\n", "# Load the ONNX model\n", "onnx_model = onnx.load(os.path.join('resources', 'single_relu.onnx'))\n", "print(onnx_model)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/examples/save_model.ipynb0000664000000000000000000000253613655345213017177 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The model is saved.\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import onnx\n", "import os\n", "\n", "\n", "# Preprocessing: load the old model\n", "old_model_path = os.path.join('resources', 'single_relu.onnx')\n", "onnx_model = onnx.load(old_model_path)\n", "\n", "# Preprocessing: get the path to the saved model\n", "new_model_path = os.path.join('resources', 'single_relu_new.onnx')\n", "\n", "# Save the ONNX model\n", "onnx.save(onnx_model, new_model_path)\n", "\n", "print('The model is saved.')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/examples/np_array_tensorproto.ipynb0000664000000000000000000000773313655345213021356 0ustar rootroot{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Original Numpy array:\n", "[[ 1. 2. 3.]\n", " [ 4. 5. 6.]]\n", "\n" ] } ], "source": [ "from __future__ import absolute_import\n", "from __future__ import division\n", "from __future__ import print_function\n", "from __future__ import unicode_literals\n", "\n", "import numpy\n", "import onnx\n", "import os\n", "from onnx import numpy_helper\n", "from distutils.version import LooseVersion\n", "\n", "\n", "# Preprocessing: create a Numpy array\n", "numpy_array = numpy.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=float)\n", "if LooseVersion(numpy.version.version) < LooseVersion('1.14'):\n", " print('Original Numpy array:\\n{}\\n'.format(numpy.array2string(numpy_array)))\n", "else:\n", " print('Original Numpy array:\\n{}\\n'.format(numpy.array2string(numpy_array, legacy='1.13')))" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TensorProto:\n", "dims: 2\n", "dims: 3\n", "data_type: 11\n", "raw_data: \"\\000\\000\\000\\000\\000\\000\\360?\\000\\000\\000\\000\\000\\000\\000@\\000\\000\\000\\000\\000\\000\\010@\\000\\000\\000\\000\\000\\000\\020@\\000\\000\\000\\000\\000\\000\\024@\\000\\000\\000\\000\\000\\000\\030@\"\n", "\n" ] } ], "source": [ "# Convert the Numpy array to a TensorProto\n", "tensor = numpy_helper.from_array(numpy_array)\n", "print('TensorProto:\\n{}'.format(tensor))" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "After round trip, Numpy array:\n", "[[ 1. 2. 3.]\n", " [ 4. 5. 6.]]\n", "\n" ] } ], "source": [ "# Convert the TensorProto to a Numpy array\n", "new_array = numpy_helper.to_array(tensor)\n", "if LooseVersion(numpy.version.version) < LooseVersion('1.14'):\n", " print('After round trip, Numpy array:\\n{}\\n'.format(numpy.array2string(numpy_array)))\n", "else:\n", " print('After round trip, Numpy array:\\n{}\\n'.format(numpy.array2string(numpy_array, legacy='1.13')))" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# Save the TensorProto\n", "with open(os.path.join('resources', 'tensor.pb'), 'wb') as f:\n", " f.write(tensor.SerializeToString())" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "After saving and loading, new TensorProto:\n", "dims: 2\n", "dims: 3\n", "data_type: 11\n", "raw_data: \"\\000\\000\\000\\000\\000\\000\\360?\\000\\000\\000\\000\\000\\000\\000@\\000\\000\\000\\000\\000\\000\\010@\\000\\000\\000\\000\\000\\000\\020@\\000\\000\\000\\000\\000\\000\\024@\\000\\000\\000\\000\\000\\000\\030@\"\n", "\n" ] } ], "source": [ "# Load the TensorProto\n", "new_tensor = onnx.TensorProto()\n", "with open(os.path.join('resources', 'tensor.pb'), 'rb') as f:\n", " new_tensor.ParseFromString(f.read())\n", "print('After saving and loading, new TensorProto:\\n{}'.format(new_tensor))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.5" } }, "nbformat": 4, "nbformat_minor": 2 } onnx-1.7.0/onnx/onnx_pb.h0000664000000000000000000000332413655345213014010 0ustar rootroot// Copyright (c) ONNX Project Contributors. // Licensed under the MIT license. #ifndef ONNX_ONNX_PB_H #define ONNX_ONNX_PB_H // Defines ONNX_EXPORT and ONNX_IMPORT. On Windows, this corresponds to // different declarations (dllexport and dllimport). On Linux/Mac, it just // resolves to the same "default visibility" setting. #if defined(_MSC_VER) #if defined(ONNX_BUILD_SHARED_LIBS) || defined(ONNX_BUILD_MAIN_LIB) #define ONNX_EXPORT __declspec(dllexport) #define ONNX_IMPORT __declspec(dllimport) #else #define ONNX_EXPORT #define ONNX_IMPORT #endif #else #if defined(__GNUC__) #define ONNX_EXPORT __attribute__((__visibility__("default"))) #else #define ONNX_EXPORT #endif #define ONNX_IMPORT ONNX_EXPORT #endif // ONNX_API is a macro that, depends on whether you are building the // main ONNX library or not, resolves to either ONNX_EXPORT or // ONNX_IMPORT. // // This is used in e.g. ONNX's protobuf files: when building the main library, // it is defined as ONNX_EXPORT to fix a Windows global-variable-in-dll // issue, and for anyone dependent on ONNX it will be defined as // ONNX_IMPORT. ONNX_BUILD_MAIN_LIB can also be set when being built // statically if ONNX is being linked into a shared library that wants // to export the ONNX APIs and classes. // // More details on Windows dllimport / dllexport can be found at // https://msdn.microsoft.com/en-us/library/3y1sfaz2.aspx // // This solution is similar to // https://github.com/pytorch/pytorch/blob/master/caffe2/core/common.h #if defined(ONNX_BUILD_SHARED_LIBS) || defined(ONNX_BUILD_MAIN_LIB) #define ONNX_API ONNX_EXPORT #else #define ONNX_API ONNX_IMPORT #endif #ifdef ONNX_ML #include "onnx/onnx-ml.pb.h" #else #include "onnx/onnx.pb.h" #endif #endif // ! ONNX_ONNX_PB_H onnx-1.7.0/onnx/onnxifi_loader.c0000664000000000000000000001166013655345213015342 0ustar rootroot#ifdef _WIN32 #include #else #include #include #include #include #endif #include "onnx/onnxifi_loader.h" /* ONNXIFI_LOADER_LOGGING macro enables/disables logging. Its OFF by default. */ #ifndef ONNXIFI_LOADER_LOGGING #define ONNXIFI_LOADER_LOGGING 0 #endif #if ONNXIFI_LOADER_LOGGING #if defined(__ANDROID__) #include /* Tag used for logging on Android */ #define ONNXIFI_LOADER_ANDROID_LOG_TAG "ONNXIFI-LOADER" #else #include #endif #endif #if defined(__APPLE__) #define ONNXIFI_LIBRARY_NAME "libonnxifi.dylib" #elif defined(_WIN32) #define ONNXIFI_LIBRARY_NAME L"onnxifi.dll" #else #define ONNXIFI_LIBRARY_NAME "libonnxifi.so" #endif /* Order must match declaration order in onnxifi_library structure */ static const char onnxifi_function_names[] = "onnxGetBackendIDs\0" "onnxReleaseBackendID\0" "onnxGetBackendInfo\0" "onnxGetBackendCompatibility\0" "onnxInitBackend\0" "onnxReleaseBackend\0" "onnxInitEvent\0" "onnxSignalEvent\0" "onnxGetEventState\0" "onnxWaitEvent\0" "onnxReleaseEvent\0" "onnxInitGraph\0" "onnxSetGraphIO\0" "onnxRunGraph\0" "onnxReleaseGraph\0" #ifdef ONNXIFI_ENABLE_EXT "onnxGetExtensionFunctionAddress\0" #endif ; int ONNXIFI_ABI onnxifi_load( uint32_t flags, #ifdef _WIN32 const wchar_t* path, #else const char* path, #endif struct onnxifi_library* onnx) { size_t i; const char* function_name; if (onnx == NULL) { return 0; } #ifdef _WIN32 ZeroMemory(onnx, sizeof(struct onnxifi_library)); #else memset(onnx, 0, sizeof(struct onnxifi_library)); #endif if (!(flags & ONNXIFI_LOADER_FLAG_VERSION_1_0)) { /* Unknown ONNXIFI version requested */ return 0; } if (path == NULL) { path = ONNXIFI_LIBRARY_NAME; } #ifdef _WIN32 onnx->handle = (void*) LoadLibraryExW(path, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); #else /* Clear libdl error state */ dlerror(); onnx->handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); #endif if (onnx->handle == NULL) { #if ONNXIFI_LOADER_LOGGING #if defined(__ANDROID__) __android_log_print( ANDROID_LOG_ERROR, ONNXIFI_LOADER_ANDROID_LOG_TAG, "failed to load %s: %s", path, dlerror()); #elif defined(_WIN32) fprintf( stderr, "Error: failed to load %S: error %u\n", path, (unsigned long) GetLastError()); #else fprintf(stderr, "Error: failed to load %s: %s\n", path, dlerror()); #endif #endif /* ONNXIFI_LOADER_LOGGING */ goto failed; } function_name = onnxifi_function_names; for (i = 0; i < ONNXIFI_LOADER_FUNCTION_COUNT; i++) { #ifdef _WIN32 onnx->functions[i] = GetProcAddress((HMODULE) onnx->handle, function_name); #else onnx->functions[i] = dlsym(onnx->handle, function_name); #endif if (onnx->functions[i] == NULL) { #if ONNXIFI_LOADER_LOGGING #if defined(__ANDROID__) __android_log_print( ANDROID_LOG_ERROR, ONNXIFI_LOADER_ANDROID_LOG_TAG, "failed to find function %s in %s: %s", function_name, ONNXIFI_LIBRARY_NAME, dlerror()); #elif defined(_WIN32) fprintf( stderr, "Error: failed to find function %s in %s: error %u\n", function_name, ONNXIFI_LIBRARY_NAME, (unsigned long) GetLastError()); #else fprintf( stderr, "Error: failed to find function %s in %s: %s\n", function_name, ONNXIFI_LIBRARY_NAME, dlerror()); #endif #endif /* ONNXIFI_LOADER_LOGGING */ goto failed; } #ifdef _WIN32 function_name += lstrlenA(function_name); #else function_name += strlen(function_name); #endif /* Skip null-terminator */ function_name += 1; } onnx->flags = flags & ONNXIFI_LOADER_FLAG_VERSION_MASK; return 1; failed: onnxifi_unload(onnx); return 0; } void ONNXIFI_ABI onnxifi_unload(struct onnxifi_library* onnx) { if (onnx != NULL) { if (onnx->handle != NULL) { #ifdef _WIN32 if (FreeLibrary((HMODULE) onnx->handle) == FALSE) { #if ONNXIFI_LOADER_LOGGING fprintf( stderr, "Error: failed to unload library %s: error %u\n", ONNXIFI_LIBRARY_NAME, (unsigned long) GetLastError()); #endif /* ONNXIFI_LOADER_LOGGING */ } #else /* !defined(_WIN32) */ /* Clear libdl error state */ dlerror(); if (dlclose(onnx->handle) != 0) { #if ONNXIFI_LOADER_LOGGING #if defined(__ANDROID__) __android_log_print( ANDROID_LOG_ERROR, ONNXIFI_LOADER_ANDROID_LOG_TAG, "failed to unload %s: %s", ONNXIFI_LIBRARY_NAME, dlerror()); #else fprintf( stderr, "Error: failed to unload %s: %s\n", ONNXIFI_LIBRARY_NAME, dlerror()); #endif #endif /* ONNXIFI_LOADER_LOGGING */ } #endif /* !defined(_WIN32) */ } #ifdef _WIN32 ZeroMemory(onnx, sizeof(struct onnxifi_library)); #else memset(onnx, 0, sizeof(struct onnxifi_library)); #endif } } onnx-1.7.0/onnx/shape_inference/0000775000000000000000000000000013655345213015310 5ustar rootrootonnx-1.7.0/onnx/shape_inference/implementation.cc0000664000000000000000000003645713655345213020663 0ustar rootroot#include "onnx/shape_inference/implementation.h" #include "onnx/string_utils.h" namespace ONNX_NAMESPACE { namespace shape_inference { namespace { std::string getValueCaseString(const TypeProto& type) { switch (type.value_case()) { case TypeProto::ValueCase::kTensorType: return "tensor_type"; case TypeProto::ValueCase::kSequenceType: return "sequence_type"; case TypeProto::ValueCase::kMapType: return "map_type"; #ifdef ONNX_ML case TypeProto::ValueCase::kOpaqueType: return "opaque_type"; case TypeProto::ValueCase::kSparseTensorType: return "sparse_tensor_type"; #endif case TypeProto::ValueCase::VALUE_NOT_SET: return "NOT_SET"; default: return ONNX_NAMESPACE::to_string(type.value_case()); } } std::string getElemTypeString(const TypeProto_Tensor& type) { #ifndef ONNX_USE_LITE_PROTO const std::string type_str = TensorProto::DataType_Name( static_cast(type.elem_type())); if (!type_str.empty()) { return type_str; } #endif return ONNX_NAMESPACE::to_string(type.elem_type()); } } // namespace void checkShapesAndTypes( const TypeProto_Tensor& inferredType, const TypeProto_Tensor& existingType) { if (inferredType.elem_type() != TensorProto::UNDEFINED && existingType.elem_type() != TensorProto::UNDEFINED && existingType.elem_type() != inferredType.elem_type()) { std::stringstream ss; ss << "Inferred elem type differs from existing elem type: (" << getElemTypeString(inferredType) << ") vs (" << getElemTypeString(existingType) << ")"; throw std::runtime_error(ss.str()); } if (!inferredType.has_shape() || !existingType.has_shape()) { return; } if (inferredType.shape().dim_size() != existingType.shape().dim_size()) { std::stringstream ss; ss << "Inferred shape and existing shape differ in rank: (" << inferredType.shape().dim_size() << ") vs (" << existingType.shape().dim_size() << ")"; throw std::runtime_error(ss.str()); } for (int i = 0; i < inferredType.shape().dim_size(); ++i) { const auto& inferredDim = inferredType.shape().dim(i); const auto& existingDim = existingType.shape().dim(i); if (inferredDim.has_dim_value() && existingDim.has_dim_value() && inferredDim.dim_value() != existingDim.dim_value()) { std::stringstream ss; ss << "Inferred shape and existing shape differ in dimension " << i << ": (" << inferredDim.dim_value() << ") vs (" << existingDim.dim_value() << ")"; throw std::runtime_error(ss.str()); } } } void checkShapesAndTypes( const TypeProto& inferredType, const TypeProto& existingType) { const auto inferredTypeCase = inferredType.value_case(); const auto existingTypeCase = existingType.value_case(); if (inferredTypeCase != existingTypeCase) { fail_type_inference( "type case mismatch. existing=", getValueCaseString(existingType), " inferred=", getValueCaseString(inferredType)); } if (inferredType.has_tensor_type() && existingType.has_tensor_type()) { checkShapesAndTypes(inferredType.tensor_type(), existingType.tensor_type()); } else if (inferredType.has_sequence_type() && existingType.has_sequence_type()) { checkShapesAndTypes(inferredType.sequence_type().elem_type(), existingType.sequence_type().elem_type()); } else { fail_type_inference( "type case unsupported. existing=", existingTypeCase, " inferred=", inferredTypeCase); } } void mergeShapesAndTypes( const TypeProto_Tensor& inferredType, TypeProto_Tensor* existingType) { if (inferredType.elem_type() != TensorProto::UNDEFINED) { if (existingType->elem_type() == TensorProto::UNDEFINED) { existingType->set_elem_type(inferredType.elem_type()); } else if (existingType->elem_type() != inferredType.elem_type()) { fail_type_inference( "type mismatch. existing=", getElemTypeString(*existingType), " inferred=", getElemTypeString(inferredType)); } } if (!inferredType.has_shape()) { return; } if (!existingType->has_shape()) { // Ensure the shape is initialized. Note that this must be done // even for (zero-dimensional) scalars. existingType->mutable_shape(); for (int j = 0; j < inferredType.shape().dim_size(); ++j) { existingType->mutable_shape()->add_dim(); } } for (int i = 0; i < inferredType.shape().dim_size(); ++i) { const auto& inferredDim = inferredType.shape().dim(i); auto* existingDim = existingType->mutable_shape()->mutable_dim(i); if (!existingDim->has_dim_value()) { *existingDim = inferredDim; } } } void mergeShapesAndTypes( const TypeProto& inferredType, TypeProto* existingType) { if (inferredType.has_tensor_type()) { mergeShapesAndTypes(inferredType.tensor_type(), existingType->mutable_tensor_type()); } else if (inferredType.has_sequence_type()) { mergeShapesAndTypes( inferredType.sequence_type().elem_type(), existingType->mutable_sequence_type()->mutable_elem_type()); } } static void InferShapesImpl( GraphProto* g, const std::unordered_map& outer_scope_value_types_by_name, const std::unordered_map& opset_imports, bool check_type, const ISchemaRegistry* schema_registry = OpSchemaRegistry::Instance() ) { std::unordered_map valueTypesByName{ outer_scope_value_types_by_name}; GraphInferenceContext graphInferenceContext{ valueTypesByName, opset_imports, schema_registry}; for (auto& vi : *g->mutable_value_info()) { if (vi.has_type()) valueTypesByName[vi.name()] = vi.mutable_type(); } for (auto& vi : *g->mutable_input()) { if (vi.has_type()) valueTypesByName[vi.name()] = vi.mutable_type(); } for (auto& vi : *g->mutable_output()) { if (vi.has_type()) valueTypesByName[vi.name()] = vi.mutable_type(); } std::unordered_map inputDataByName; for (const auto& tp : g->initializer()) { inputDataByName[tp.name()] = &tp; } // Collect data from constant nodes. for (const auto& n : g->node()) { if (n.op_type() != "Constant" || n.output().size() != 1) { continue; } for (const auto& attr : n.attribute()) { if (attr.name() == "value" && attr.type() == AttributeProto::TENSOR && attr.has_t()) { inputDataByName[n.output(0)] = &attr.t(); } } } for (auto& n : *g->mutable_node()) { // Resolve domain for node auto dit = opset_imports.find(n.domain()); if (dit == opset_imports.end()) { continue; } auto domain_version = dit->second; const auto schema = schema_registry->GetSchema(n.op_type(), domain_version, n.domain()); InferenceContextImpl ctx( n, valueTypesByName, inputDataByName, &graphInferenceContext); if (!schema) { continue; } else if (schema->has_type_and_shape_inference_function()){ try { schema->GetTypeAndShapeInferenceFunction()(ctx); } catch (const ONNX_NAMESPACE::InferenceError& ex) { (void)ex; // Continue with inference for remaining nodes continue; } } else if (schema->HasFunction()) { try { InferShapeForFunctionNode( schema->GetFunction(), schema_registry, ctx); } catch (const ONNX_NAMESPACE::InferenceError& function_ex) { (void)function_ex; continue; } } else { // Continue with inference for remaining nodes continue; } try { if (check_type) { schema->CheckInputOutputType(ctx); } for (int i = 0; i < n.output_size(); ++i) { const auto* inferredType = ctx.getOutputType(i); if (!inferredType->has_tensor_type() && !inferredType->has_sequence_type()) { continue; } if (inferredType->has_tensor_type()) { const auto& inferredTensorType = inferredType->tensor_type(); // Bail out early if shape inference does nothing useful. if (inferredTensorType.elem_type() == TensorProto::UNDEFINED && !inferredTensorType.has_shape()) { continue; } } // Find any pre-existing type and shape info. If there is such, // then check for compatibility with the inferred // information. Otherwise, initialize it in an empty state. auto iter = valueTypesByName.find(n.output(i)); TypeProto* existingType = nullptr; if (iter != valueTypesByName.end()) { existingType = iter->second; checkShapesAndTypes(*inferredType, *existingType); } else { auto vi = g->add_value_info(); vi->set_name(n.output(i)); existingType = vi->mutable_type(); } // Now we can merge pre-existing and inferred info, without // further need for error-checking. mergeShapesAndTypes(*inferredType, existingType); // Make merged info available to further inference. valueTypesByName[n.output(i)] = existingType; } } catch (const std::runtime_error& err) { std::string op_name = n.has_name() ? n.name() : "no name"; std::cerr << "(op_type:" << n.op_type() << ", name:" << n.name() << "): " << err.what() << '\n'; throw; } } } void InferShapes( GraphProto* g, const std::unordered_map& opset_imports, bool check_type, const ISchemaRegistry* schema_registry ) { InferShapesImpl( g, std::unordered_map(0), opset_imports, check_type, schema_registry); } void InferShapes( ModelProto& m, bool check_type, const ISchemaRegistry* schema_registry ) { std::unordered_map opset_imports; for (const auto& opset_import : m.opset_import()) { opset_imports[opset_import.domain()] = static_cast(opset_import.version()); } auto* g = m.mutable_graph(); InferShapesImpl( g, std::unordered_map(0), opset_imports, check_type, schema_registry); } void InferShapeForFunctionNode( const FunctionProto* func, const ISchemaRegistry* schema_registry, InferenceContext& ctx) { int domain_version = (int)func->since_version(); GraphProto g; // Get a temporary tensor-shape map std::unordered_map temp_valueTypesByName; std::vector temp_types_cache(func->input_size()); for (int i = 0; i < func->input_size(); ++i) { temp_types_cache[i] = *ctx.getInputType(i); temp_valueTypesByName[func->input().Get(i)] = &temp_types_cache[i]; } // Get a temporary initial value map std::unordered_map temp_initializersByName; for (int i = 0; i < static_cast(ctx.getNumInputs()); ++i) { if (ctx.getInputData(i) != nullptr && i < func->input_size()) { temp_initializersByName[func->input().Get(i)] = ctx.getInputData(i); } } std::unordered_map attr_map; for (auto& attr : func->attribute()) { if (ctx.getAttribute(attr) != nullptr) { attr_map[attr] = ctx.getAttribute(attr); } } for (auto& n : func->node()) { const auto schema = schema_registry->GetSchema(n.op_type(), domain_version, n.domain()); if (!schema) { return; } NodeProto copy_n(n); // Add attribute information into the temporary node copy_n.clear_attribute(); for (const auto& attr : n.attribute()) { if (attr.has_ref_attr_name()) { if (attr_map.count(attr.ref_attr_name())) { auto copy_attr = *attr_map[attr.ref_attr_name()]; copy_attr.set_name(attr.name()); copy_n.add_attribute()->CopyFrom(copy_attr); } } else { copy_n.add_attribute()->CopyFrom(attr); } } InferenceContextImpl temp_ctx( copy_n, temp_valueTypesByName, temp_initializersByName); schema->GetTypeAndShapeInferenceFunction()(temp_ctx); for (int i = 0; i < copy_n.output_size(); ++i) { if (!temp_ctx.getOutputType(i)->has_tensor_type()) { continue; } const auto& inferredType = temp_ctx.getOutputType(i)->tensor_type(); // Bail out early if shape inference does nothing useful. if (inferredType.elem_type() == TensorProto::UNDEFINED && !inferredType.has_shape()) { continue; } // Checking, Storing the inferred information auto iter = temp_valueTypesByName.find(n.output(i)); TypeProto* existingType = nullptr; if (iter != temp_valueTypesByName.end()) { existingType = iter->second; checkShapesAndTypes(inferredType, existingType->tensor_type()); } else { // Store the inferred type info in the // subgraph temporarily auto vi = g.add_value_info(); vi->set_name(copy_n.output(i)); existingType = vi->mutable_type(); } mergeShapesAndTypes(inferredType, existingType->mutable_tensor_type()); // Make merged info available to further inference. temp_valueTypesByName[copy_n.output(i)] = existingType; } } for (int i = 0; i < func->output_size(); ++i) { std::string output_name = func->output().Get(i); // Skip if no type inferred for the tensor if (!temp_valueTypesByName.count(output_name)) { continue; } // Copy the type info from subgraph to ctx // to pass back to maingraph auto type = ctx.getOutputType(i)->mutable_tensor_type(); type->CopyFrom(temp_valueTypesByName[output_name]->tensor_type()); } } std::vector GraphInferencerImpl::doInferencing( const std::vector& inputTypes, const std::vector& inputData) { int numInputs = int(inputTypes.size()); if (g_->input_size() != numInputs) fail_shape_inference( "Graph has ", g_->input_size(), " inputs but ", numInputs, " were provided"); for (int i = 0, end = numInputs; i < end; ++i) { const TypeProto* inferredInput = inputTypes[i]; if (!inferredInput) continue; TypeProto* graphInput = g_->mutable_input(i)->mutable_type(); if (!graphInput->has_tensor_type()) { continue; } if (!inferredInput->has_tensor_type()) fail_type_inference( "Graph input #", i, " is tensor type, but provided type is ", getValueCaseString(*inferredInput)); const auto& inferredType = inferredInput->tensor_type(); // Bail out early if shape inference does nothing useful. if (inferredType.elem_type() == TensorProto::UNDEFINED && !inferredType.has_shape()) { continue; } mergeShapesAndTypes(inferredType, graphInput->mutable_tensor_type()); } // future: pass inputData into InferShapes either directly, or indirectly by // updating initializers that match subgraph inputs. (void)inputData; InferShapesImpl( g_, *context_->outer_scope_value_types_by_name, // never null context_->opset_imports, false, context_->schema_registry); std::vector graphOutputTypes; for (const ValueInfoProto& output : g_->output()) { graphOutputTypes.push_back(&output.type()); } return graphOutputTypes; } } // namespace shape_inference } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/shape_inference/implementation.h0000664000000000000000000001454413655345213020516 0ustar rootroot#pragma once #include "onnx/defs/function.h" #include "onnx/defs/schema.h" #include "onnx/proto_utils.h" #include "onnx/string_utils.h" namespace ONNX_NAMESPACE { namespace shape_inference { struct GraphInferenceContext { GraphInferenceContext( const std::unordered_map& outer_scope_value_types_by_name_in, const std::unordered_map opset_imports_in, const ISchemaRegistry* schema_registry_in = OpSchemaRegistry::Instance()) : outer_scope_value_types_by_name{&outer_scope_value_types_by_name_in}, opset_imports{opset_imports_in}, schema_registry{schema_registry_in} {} const std::unordered_map* outer_scope_value_types_by_name; const std::unordered_map opset_imports; const ISchemaRegistry* schema_registry; }; class GraphInferencerImpl : public GraphInferencer { public: GraphInferencerImpl(GraphProto& g, const GraphInferenceContext& context) : g_{&g}, context_{&context} {} std::vector doInferencing( const std::vector& inputTypes, const std::vector& inputData) override; private: GraphProto* g_; const GraphInferenceContext* context_; }; struct InferenceContextImpl : public InferenceContext { InferenceContextImpl( NodeProto& n, const std::unordered_map& valueTypesByName, const std::unordered_map& inputDataByName, const GraphInferenceContext* graphInferenceContext = nullptr) : graphInferenceContext_{graphInferenceContext} { for (auto& attr : *n.mutable_attribute()) { attributesByName_[attr.name()] = &attr; if (attr.has_g()) { // need a mutable GraphProto to run inferencing on this attribute graphProtoAttributesByName_[attr.name()] = attr.mutable_g(); } } for (const auto& input : n.input()) { auto valueTypesIter = valueTypesByName.find(input); if (valueTypesIter != valueTypesByName.end()) { allInputTypes_.push_back(valueTypesIter->second); } else { allInputTypes_.push_back(nullptr); } const auto inputDataIter = inputDataByName.find(input); if (inputDataIter != inputDataByName.cend()) { allInputData_.push_back(inputDataIter->second); } else { allInputData_.push_back(nullptr); } } allOutputTypes_.resize(n.output_size()); } const AttributeProto* getAttribute(const std::string& name) const override { auto iter = attributesByName_.find(name); if (iter == attributesByName_.end()) { return nullptr; } else { return iter->second; } } size_t getNumInputs() const override { return allInputTypes_.size(); } const TypeProto* getInputType(size_t index) const override { if (index >= allInputTypes_.size()) { throw std::runtime_error( "input " + ONNX_NAMESPACE::to_string(index) + " is out of bounds"); } return allInputTypes_[index]; } const TensorProto* getInputData(size_t index) const override { if (index >= allInputData_.size()) { throw std::runtime_error( "input " + ONNX_NAMESPACE::to_string(index) + " is out of bounds"); } return allInputData_[index]; } size_t getNumOutputs() const override { return allOutputTypes_.size(); } TypeProto* getOutputType(size_t index) override { if (index >= allOutputTypes_.size()) { throw std::runtime_error( "output " + ONNX_NAMESPACE::to_string(index) + " is out of bounds"); } return &allOutputTypes_[index]; } GraphInferencer* getGraphAttributeInferencer( const std::string& attr_name) override { if (!graphInferenceContext_) { fail_type_inference( "GraphProto attribute inferencing is not enabled in this InferenceContextImpl instance."); } GraphInferencer* inferencer = nullptr; auto entry = graphAttributeInferencers_.find(attr_name); if (entry == graphAttributeInferencers_.cend()) { // create GraphInferencer instance auto attrNameToGraphProto = graphProtoAttributesByName_.find(attr_name); if (attrNameToGraphProto == graphProtoAttributesByName_.cend()) { fail_type_inference( "Attribute ", attr_name, " does not contain a graph."); } std::unique_ptr new_inferencer{new GraphInferencerImpl( *attrNameToGraphProto->second, *graphInferenceContext_)}; inferencer = new_inferencer.get(); graphAttributeInferencers_.emplace(attr_name, std::move(new_inferencer)); } else { inferencer = entry->second.get(); } return inferencer; } std::vector allInputData_; std::unordered_map attributesByName_; std::unordered_map graphProtoAttributesByName_; std::vector allInputTypes_; std::vector allOutputTypes_; const GraphInferenceContext* graphInferenceContext_; // mutable as internal cache of GraphInferencer instances mutable std::unordered_map> graphAttributeInferencers_; }; void checkShapesAndTypes( const TypeProto_Tensor& inferredType, const TypeProto_Tensor& existingType); void checkShapesAndTypes( const TypeProto_Sequence& inferredType, const TypeProto_Sequence& existingType); void checkShapesAndTypes( const TypeProto& inferredType, const TypeProto& existingType); void mergeShapesAndTypes( const TypeProto_Tensor& inferredType, TypeProto_Tensor* existingType); void mergeShapesAndTypes( const TypeProto_Sequence& inferredType, TypeProto_Tensor* existingType); void mergeShapesAndTypes( const TypeProto& inferredType, TypeProto* existingType); void InferShapes( ModelProto& m, bool check_type = false, const ISchemaRegistry* schema_registry = OpSchemaRegistry::Instance() ); void InferShapes( GraphProto* g, const std::unordered_map& opset_imports, bool check_type = false, const ISchemaRegistry* schema_registry = OpSchemaRegistry::Instance() ); void InferShapeForFunctionNode( const FunctionProto* func, const ISchemaRegistry* schema_registry, InferenceContext& ctx); } // namespace shape_inference } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/onnxifi.h0000664000000000000000000027206113655345213014025 0ustar rootroot#ifndef ONNXIFI_H #define ONNXIFI_H 1 #ifdef __cplusplus extern "C" { #endif #if defined(_WIN32) && defined(_M_IX86) /* Windows x86 */ #define ONNXIFI_ABI __stdcall #elif defined(__i386__) /* Linux x86 */ #define ONNXIFI_ABI __attribute__((__cdecl__)) #else #define ONNXIFI_ABI #endif #ifndef ONNXIFI_PUBLIC #if defined(__ELF__) #define ONNXIFI_PUBLIC __attribute__((__visibility__("default"))) #elif defined(__MACH__) #define ONNXIFI_PUBLIC __attribute__((__visibility__("default"))) #elif defined(_WIN32) && defined(__GNUC__) #ifdef ONNXIFI_BUILD_LIBRARY #define ONNXIFI_PUBLIC __attribute__((__dllexport__)) #else #define ONNXIFI_PUBLIC __attribute__((__dllimport__)) #endif #elif defined(_WIN32) #ifdef ONNXIFI_BUILD_LIBRARY #define ONNXIFI_PUBLIC __declspec(dllexport) #else #define ONNXIFI_PUBLIC __declspec(dllimport) #endif #else #define ONNXIFI_PUBLIC #endif #endif #ifndef ONNXIFI_CHECK_RESULT #if defined(__GNUC__) && (__GNUC__ >= 4) #define ONNXIFI_CHECK_RESULT __attribute__((__warn_unused_result__)) #elif defined(_MSC_VER) && (_MSC_VER >= 1700) #define ONNXIFI_CHECK_RESULT _Check_return_ #else #define ONNXIFI_CHECK_RESULT #endif #endif #include #if !defined(ONNXIFI_NO_STDINT_H) #if defined(_MSC_VER) && (_MSC_VER < 1600) typedef signed __int8 int8_t; typedef unsigned __int8 uint8_t; typedef signed __int16 int16_t; typedef unsigned __int16 uint16_t; typedef signed __int32 int32_t; typedef unsigned __int32 uint32_t; typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; #else #include #endif #endif /* !defined(ONNXIFI_NO_STDINT_H) */ /** * Opaque ONNXIFI backend ID. * * ONNXIFI backend is a combination of software layer and hardware device used * to run an ONNX graph. Backend ID uniquely identifies a backend for the life- * time of the process (i.e. no two hardware devices, software layers, or * combinations of both can have the same backend ID). Backend ID stays valid * even if the hardware device used by the backend disconnects from the system. */ typedef void* onnxBackendID; /** * Opaque ONNXIFI backend handle. * ONNXIFI backend is a combination of software layer and hardware device used * to run an ONNX graph. */ typedef void* onnxBackend; /** Opaque ONNXIFI graph handle. */ typedef void* onnxGraph; /** Opaque ONNXIFI even handle. */ typedef void* onnxEvent; /** Return code for ONNXIFI functions */ typedef int32_t onnxStatus; /** * Type for enumeration values. * * The low 32 bits are reserved for standardized ONNXIFI values. * The high 32 bits are reserved for vendor-specific extensions. Applications * must check for specific vendor extensions before interpreting these bits. */ typedef uint64_t onnxEnum; /** * Type for bit fields. * * The low 32 bits are reserved for standardized ONNXIFI values. * The high 32 bits are reserved for vendor-specific extensions. Applications * must check for specific vendor extensions before interpreting these bits. */ typedef uint64_t onnxBitfield; /** * Type for pointers or handles for memory buffers. * This type is intended to work not only for CPU-addressable memory, but also * for device memory. uint64_t ensures the API can accommodate Vulkan buffers. */ typedef uint64_t onnxPointer; #define ONNXIFI_STATUS_SUCCESS 0x0000 #define ONNXIFI_STATUS_FALLBACK 0x0001 #define ONNXIFI_STATUS_INVALID_ID 0x0101 #define ONNXIFI_STATUS_INVALID_SIZE 0x0102 #define ONNXIFI_STATUS_INVALID_POINTER 0x0103 #define ONNXIFI_STATUS_INVALID_PROTOBUF 0x0104 #define ONNXIFI_STATUS_INVALID_MODEL 0x0105 #define ONNXIFI_STATUS_INVALID_BACKEND 0x0106 #define ONNXIFI_STATUS_INVALID_GRAPH 0x0107 #define ONNXIFI_STATUS_INVALID_EVENT 0x0108 #define ONNXIFI_STATUS_INVALID_STATE 0x0109 #define ONNXIFI_STATUS_INVALID_NAME 0x010A #define ONNXIFI_STATUS_INVALID_SHAPE 0x010B #define ONNXIFI_STATUS_INVALID_DATATYPE 0x010C #define ONNXIFI_STATUS_INVALID_MEMORY_TYPE 0x010D #define ONNXIFI_STATUS_INVALID_MEMORY_LOCATION 0x010E #define ONNXIFI_STATUS_INVALID_FENCE_TYPE 0x010F #define ONNXIFI_STATUS_INVALID_PROPERTY 0x0110 #define ONNXIFI_STATUS_UNSUPPORTED_TAG 0x0201 #define ONNXIFI_STATUS_UNSUPPORTED_VERSION 0x0202 #define ONNXIFI_STATUS_UNSUPPORTED_OPERATOR 0x0203 #define ONNXIFI_STATUS_UNSUPPORTED_ATTRIBUTE 0x0204 #define ONNXIFI_STATUS_UNSUPPORTED_SHAPE 0x0205 #define ONNXIFI_STATUS_UNSUPPORTED_DATATYPE 0x0206 #define ONNXIFI_STATUS_UNSUPPORTED_MEMORY_TYPE 0x0207 #define ONNXIFI_STATUS_UNSUPPORTED_FENCE_TYPE 0x0208 #define ONNXIFI_STATUS_UNSUPPORTED_PROPERTY 0x0209 #define ONNXIFI_STATUS_UNIDENTIFIED_NAME 0x0301 #define ONNXIFI_STATUS_MISMATCHING_SHAPE 0x0302 #define ONNXIFI_STATUS_MISMATCHING_DATATYPE 0x0303 #define ONNXIFI_STATUS_NO_SYSTEM_MEMORY 0x0401 #define ONNXIFI_STATUS_NO_DEVICE_MEMORY 0x0402 #define ONNXIFI_STATUS_NO_SYSTEM_RESOURCES 0x0403 #define ONNXIFI_STATUS_NO_DEVICE_RESOURCES 0x0404 #define ONNXIFI_STATUS_BACKEND_UNAVAILABLE 0x0405 #define ONNXIFI_STATUS_INTERNAL_ERROR 0x0406 /** * State of an ONNXIFI event object. * * Possible values: * ONNXIFI_EVENT_STATE_INVALID * ONNXIFI_EVENT_STATE_NONSIGNALLED * ONNXIFI_EVENT_STATE_SIGNALLED */ typedef int32_t onnxEventState; /** * State for an invalid onnxEvent. */ #define ONNXIFI_EVENT_STATE_INVALID 0 /** * Non-signalled onnxEvent state. * onnxInitEvent creates events in non-signalled state. */ #define ONNXIFI_EVENT_STATE_NONSIGNALLED 0x16BD /** * Signalled onnxEvent state. * onnxSignalEvent changes event state to signalled. */ #define ONNXIFI_EVENT_STATE_SIGNALLED 0x3395 /** Special-purpose accelerator for neural network */ #define ONNXIFI_DEVICE_TYPE_NPU 0x01 /** Digital signal processor */ #define ONNXIFI_DEVICE_TYPE_DSP 0x02 /** Graphics accelerator */ #define ONNXIFI_DEVICE_TYPE_GPU 0x04 /** General-purpose central processor */ #define ONNXIFI_DEVICE_TYPE_CPU 0x08 /** Field-programmable gate array */ #define ONNXIFI_DEVICE_TYPE_FPGA 0x10 /** * Heterogeneous backend whichinternally arbitrates or distributes work between * multiple device types. */ #define ONNXIFI_DEVICE_TYPE_HETEROGENEOUS 0x20 /** * The backend supports multi-threaded access to ONNXIFI backend, graph, and * event objects. E.g. onnxInitGraph can be called on a different thread than * onnxInitBackend. * * If this capability it not indicated, ONNXIFI backend, graph, and event * objects that relate to the backend must always be used on the same thread * where the backend object was initialized. */ #define ONNXIFI_CAPABILITY_THREAD_SAFE 0x01 /** * The backend supports ONNX graphs with symbolic variables in the outer * shape dimension (batch size), using TensorShapeProto.dim_param for * ModelProto.graph.input.type.shape or ModelProto.graph.output.type.shape. * * The exact numerical value of the of all input and output tensors must be specified * in the onnxSetGraphIO call(s). */ #define ONNXIFI_CAPABILITY_SYMBOLIC_BATCH_SIZE 0x02 /** * The backend supports ONNX graphs with symbolic variables in the all * shape dimensions, using TensorShapeProto.dim_param for * ModelProto.graph.input.type.shape or ModelProto.graph.output.type.shape. * * Backends with this capability also MUST support * ONNXIFI_CAPABILITY_SYMBOLIC_BATCH_SIZE capability. * * The exact numerical shape of all input and output tensors must be specified * in the onnxSetGraphIO call(s). */ #define ONNXIFI_CAPABILITY_SYMBOLIC_SIZE_TENSORS 0x04 /** * The backend supports ONNX graphs with data-dependent outer shape dimension * (batch size) of graph outputs. The ONNX graph would specify unknown outer * shape dimension (batch size) using symbolic variables, so this capability * requires ONNXIFI_CAPABILITY_SYMBOLIC_BATCH_SIZE support. * * For outputs with data-dependent outer shape dimension (batch size) the value * specified in onnxSetGraphIO call is interpreted as the upper limit. The exact * numerical batch size of the output can be retrieved by attaching a Shape * operator to the tensor with data-dependent shape and reading its output * through ONNXIFI. */ #define ONNXIFI_CAPABILITY_VARIABLE_BATCH_SIZE 0x08 /** * The backend supports ONNX graphs with data-dependent output shapes. * The ONNX graph would specify unknown output shapes using symbolic variables, * so this capability requires ONNXIFI_CAPABILITY_SYMBOLIC_SIZE_TENSORS support. * * Backends with this capability also MUST support * ONNXIFI_CAPABILITY_VARIABLE_BATCH_SIZE capability. * * For outputs with data-dependent shapes the shape specified in onnxSetGraphIO * call is interpreted as the upper limit. The exact numerical shape of the * output can be retrieved by attaching a Shape operator to the tensor with * data-dependent shape and reading its output through ONNXIFI. */ #define ONNXIFI_CAPABILITY_VARIABLE_SIZE_OUTPUTS 0x10 /** * The backend uses a hot-pluggable device, and can be disconnected at any time. * * If the underlying device disconnects from the system, subsequent operations * with the backend, or objects created on the backend, will fail with * ONNXIFI_STATUS_BACKEND_UNAVAILABLE status code. */ #define ONNXIFI_CAPABILITY_HOT_PLUGGABLE 0x20 /** * Type of the backend information. * * Possible values: * ONNXIFI_BACKEND_ONNXIFI_VERSION * ONNXIFI_BACKEND_NAME * ONNXIFI_BACKEND_VENDOR * ONNXIFI_BACKEND_VERSION * ONNXIFI_BACKEND_EXTENSIONS * ONNXIFI_BACKEND_DEVICE * ONNXIFI_BACKEND_DEVICE_TYPE * ONNXIFI_BACKEND_ONNX_IR_VERSION * ONNXIFI_BACKEND_OPSET_VERSION * ONNXIFI_BACKEND_CAPABILITIES * ONNXIFI_BACKEND_INIT_PROPERTIES * ONNXIFI_BACKEND_MEMORY_TYPES * ONNXIFI_BACKEND_GRAPH_INIT_PROPERTIES * ONNXIFI_BACKEND_SYNCHRONIZATION_TYPES * ONNXIFI_BACKEND_MEMORY_SIZE * ONNXIFI_BACKEND_MAX_GRAPH_SIZE * ONNXIFI_BACKEND_MAX_GRAPH_COUNT * ONNXIFI_BACKEND_MACS_FP32 * ONNXIFI_BACKEND_MACS_FP16 * ONNXIFI_BACKEND_MEMORY_BANDWIDTH * ONNXIFI_BACKEND_CPU_MEMORY_READ_BANDWIDTH * ONNXIFI_BACKEND_CPU_MEMORY_WRITE_BANDWIDTH * ONNXIFI_BACKEND_PCI_BUS_ID * ONNXIFI_BACKEND_PCI_DEVICE_ID * ONNXIFI_BACKEND_PCI_DOMAIN_ID * ONNXIFI_BACKEND_DIRECTX_ID * ONNXIFI_BACKEND_CUDA_INDEX * ONNXIFI_BACKEND_OPENCL_PLATFORM_ID * ONNXIFI_BACKEND_OPENCL_DEVICE_ID */ typedef int32_t onnxBackendInfo; /** * Major and minor version of ONNXIFI specification implemented by the backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: uint64_t. * The high 32 bits specify the major version. * The low 32 bits specify the minor version. * * Possible values: * UINT64_C(0x0000000100000000) for ONNXIFI 1.0 */ #define ONNXIFI_BACKEND_ONNXIFI_VERSION 0 /** * Marketing name of the backend (excluding the vendor name). * * Since ONNXIFI 1.0, backends MUST support this information query. * * This string MUST be in UTF-8 encoding and NOT locale-sensitive. * * Value type: char[], e.g.: * "Caffe2" * "Glow" */ #define ONNXIFI_BACKEND_NAME 1 /** * Name of the backend vendor. * * Since ONNXIFI 1.0, backends MUST support this information query. * * This string MUST be in UTF-8 encoding and NOT locale-sensitive. * * Value type: char[], e.g.: * "Facebook" * "Marat Dukhan" */ #define ONNXIFI_BACKEND_VENDOR 2 /** * Version of the backend software. Exact format is vendor-specific, but MUST be * unique for the software release. * * Since ONNXIFI 1.0, backends MUST support this information query. * * This string MUST be in US-ASCII encoding and NOT locale-sensitive. * * Value type: char[], e.g.: * "1.2.3" * "1.2.3.0" * "1.2.3-db3a4439d233276e25681fb4735b7f8e674dda65" */ #define ONNXIFI_BACKEND_VERSION 3 /** * Space-separated list of vendor- or device-specific extensions supported on * this backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * This string MUST be in US-ASCII encoding and NOT locale-sensitive. * * Value type: char[], e.g.: * "" * "onnx_clone_graph" * "onnx_clone_graph fb_maskrcnn" */ #define ONNXIFI_BACKEND_EXTENSIONS 4 /** * Descriptive name of the device (i.e. CPU, GPU, DSP, or NPU model). * * Since ONNXIFI 1.0, backends MUST support this information query. * * This string MUST be in UTF-8 encoding and NOT locale-sensitive. * * Value type: char[], e.g.: * "nnDuino 123" */ #define ONNXIFI_BACKEND_DEVICE 5 /** * Type of the device. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: onnxEnum. * Possible values: * ONNXIFI_DEVICE_TYPE_NPU * ONNXIFI_DEVICE_TYPE_DSP * ONNXIFI_DEVICE_TYPE_GPU * ONNXIFI_DEVICE_TYPE_CPU * ONNXIFI_DEVICE_TYPE_FPGA * ONNXIFI_DEVICE_TYPE_HETEROGENEOUS */ #define ONNXIFI_BACKEND_DEVICE_TYPE 6 /** * List of supported ONNX IR versions. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: char[], e.g.: * "3" (IR version in ONNX 1.0) * * Possible values: space-separated list of supported ONNX IR versions, * represented as decimal integers. ONNX IR versions must match values * in ONNX Version enum. */ #define ONNXIFI_BACKEND_ONNX_IR_VERSION 7 /** * List of supported operator set domains and maximum supported operator set * version for each domain. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: char[], e.g.: * "ai.onnx:1" (only operators in version 1 of default ONNX operator set) * "ai.onnx:7" (operators up to version 7 of default ONNX operator set) * "org.pytorch:2 ai.onnx:6 ai.facebook:1" * * Possible values: space-separated list of domain:max_version pairs where * domain corresponds to OperatorSetIdProto.domain and max_version * corresponds to the maximum value of OperatorSetIdProto.version supported * by the backend for this domain. The backend MUST support all previous * operator set versions as well. */ #define ONNXIFI_BACKEND_OPSET_VERSION 8 /** * Optional features supported by the backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: onnxBitfield. * Possible values: any combination of the following flags: * ONNXIFI_CAPABILITY_THREAD_SAFE * ONNXIFI_CAPABILITY_SYMBOLIC_BATCH_SIZE * ONNXIFI_CAPABILITY_SYMBOLIC_SIZE_TENSORS * ONNXIFI_CAPABILITY_VARIABLE_BATCH_SIZE * ONNXIFI_CAPABILITY_VARIABLE_SIZE_OUTPUTS * ONNXIFI_CAPABILITY_HOT_PLUGGABLE * or any vendor-specific flags in the high 32 bits of the bit field. */ #define ONNXIFI_BACKEND_CAPABILITIES 10 /** * Auxiliary initialization properties supported by the backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: onnxBitfield. * Possible values: any combination of vendor-specific flags in high 32 bits of * the bit field. */ #define ONNXIFI_BACKEND_INIT_PROPERTIES 11 /** * Memory types supported for graph inputs and outputs. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: onnxBitfield. * Possible values are any combination of the following flags: * ONNXIFI_MEMORY_TYPE_CPU (always supported) * ONNXIFI_MEMORY_TYPE_CUDA_BUFFER * ONNXIFI_MEMORY_TYPE_OPENCL_BUFFER * ONNXIFI_MEMORY_TYPE_OPENGLES_TEXTURE_2D * ONNXIFI_MEMORY_TYPE_D3D_RESOURCE * or any vendor-specific flags in the high 32 bits of the bit field. */ #define ONNXIFI_BACKEND_MEMORY_TYPES 12 /** * Auxiliary initialization properties supported by graphs on the backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: onnxBitfield. * Possible values: any combination of vendor-specific flags in high 32 bits of * the bit field. */ #define ONNXIFI_BACKEND_GRAPH_INIT_PROPERTIES 13 /** * Memory synchronization primitives supported for graph inputs and outputs. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Possible values are any combination of the following flags: * ONNXIFI_SYNCHRONIZATION_EVENT (onnxEvent, always supported) * ONNXIFI_SYNCHRONIZATION_IMPLICIT * or any vendor-specific flags in the high 32 bits of the bit field. */ #define ONNXIFI_BACKEND_SYNCHRONIZATION_TYPES 14 /** * Maximum amount of memory, in bytes, available to the use by the backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_MEMORY_SIZE 20 /** * Maximum size of network parameters, in bytes. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_MAX_GRAPH_SIZE 21 /** * Maximum number of independent network graphs supported by the backend. * * Since ONNXIFI 1.0, backends MUST support this information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_MAX_GRAPH_COUNT 22 /** * Number of FP32 multiply-accumulate operations per second delivered by the * backend. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. * If the backend does not support FP32 computation, the value MUST be 0. */ #define ONNXIFI_BACKEND_MACS_FP32 30 /** * Number of FP16 multiply-accumulate operations per second delivered by the * backend. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. * If the backend does not support FP16 computation, the value MUST be 0. */ #define ONNXIFI_BACKEND_MACS_FP16 31 /** * Bandwidth, in bytes per second, of the global memory specific to the backend * device. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_MEMORY_BANDWIDTH 35 /** * Bandwidth, in bytes per second, of transferring data from cacheable * CPU-allocated memory to the backend device. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_CPU_MEMORY_READ_BANDWIDTH 36 /** * Bandwidth, in bytes per second, of transferring data to cacheable * CPU-allocated memory from the backend device. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_CPU_MEMORY_WRITE_BANDWIDTH 37 /** * PCI bus ID of the backend device. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_PCI_BUS_ID 40 /** * PCI device ID of the backend device. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_PCI_DEVICE_ID 41 /** * PCI domain/function ID of the backend device. * * Since ONNXIFI 1.0, backends are recommended, but not required to support this * information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_PCI_DOMAIN_ID 42 /** * DirectX ID of the backend device. * * This is the value that would be returned by ID3D12Device::GetAdapterLuid() * for the hardware device used by the backend. * * Since ONNXIFI 1.0, DXGI-based backends are recommended, but not required to * support this information query. * * Value type: LUID (8 bytes). */ #define ONNXIFI_BACKEND_DIRECTX_ID 43 /** * CUDA index of the backend device. * * Since ONNXIFI 1.0, CUDA-based backends are recommended, but not required to * support this information query. * * Value type: uint64_t. */ #define ONNXIFI_BACKEND_CUDA_INDEX 44 /** * OpenCL platform ID for the backend device. * This platform ID is guaranteed to remain valid for the lifetime of ONNXIFI * objects related to the same ONNXIFI backend (backend ID, backend, graph, * event). * * Since ONNXIFI 1.0, OpenCL-based backends are recommended, but not required to * support this information query. * * Value type: cl_platform_id. */ #define ONNXIFI_BACKEND_OPENCL_PLATFORM_ID 45 /** * OpenCL device ID for the backend device. * This device ID is guaranteed to remain valid for the lifetime of ONNXIFI * objects related to the same ONNXIFI backend (backend ID, backend, graph, * event). * * Since ONNXIFI 1.0, OpenCL-based backends are recommended, but not required to * support this information query. * * Value type: cl_device_id. */ #define ONNXIFI_BACKEND_OPENCL_DEVICE_ID 46 /* Note: the data type values match ONNX TensorProto.DataType enum */ #define ONNXIFI_DATATYPE_UNDEFINED 0 #define ONNXIFI_DATATYPE_FLOAT16 10 #define ONNXIFI_DATATYPE_FLOAT32 1 #define ONNXIFI_DATATYPE_FLOAT64 11 #define ONNXIFI_DATATYPE_INT8 3 #define ONNXIFI_DATATYPE_INT16 5 #define ONNXIFI_DATATYPE_INT32 6 #define ONNXIFI_DATATYPE_INT64 7 #define ONNXIFI_DATATYPE_UINT8 2 #define ONNXIFI_DATATYPE_UINT16 4 #define ONNXIFI_DATATYPE_UINT32 12 #define ONNXIFI_DATATYPE_UINT64 13 #define ONNXIFI_DATATYPE_COMPLEX64 14 #define ONNXIFI_DATATYPE_COMPLEX128 15 #define ONNXIFI_DATATYPE_BFLOAT16 16 /** Cacheable CPU memory */ #define ONNXIFI_MEMORY_TYPE_CPU 0 /** CUDA memory buffer (allocated via cudaMalloc/cuMalloc). */ #define ONNXIFI_MEMORY_TYPE_CUDA_BUFFER 1 /** OpenCL cl_mem object for a buffer or sub-buffer. */ #define ONNXIFI_MEMORY_TYPE_OPENCL_BUFFER 2 /** OpenGL ES 2.0+ 2D Texture. */ #define ONNXIFI_MEMORY_TYPE_OPENGLES_TEXTURE_2D 4 /** Direct3D resource. */ #define ONNXIFI_MEMORY_TYPE_D3D_RESOURCE 8 /** * Terminates the list of auxiliary backend initialization properties passed to * onnxInitBackend. */ #define ONNXIFI_BACKEND_PROPERTY_NONE 0 /** * Optimization target for graphs initialized on the backend. * * Possible values: * ONNXIFI_OPTIMIZATION_HIGH_THROUGHPUT * ONNXIFI_OPTIMIZATION_LOW_LATENCY * ONNXIFI_OPTIMIZATION_LOW_POWER * ONNXIFI_OPTIMIZATION_LOW_DELAY */ #define ONNXIFI_BACKEND_PROPERTY_OPTIMIZATION 1 /** * Logging verbosity level for the backend. * * If this property is not specified during initialization, the backend should * assume ONNXIFI_LOG_LEVEL_WARNING logging verbosity level. * * Possible values: * ONNXIFI_LOG_LEVEL_ERROR * ONNXIFI_LOG_LEVEL_WARNING * ONNXIFI_LOG_LEVEL_INFO * ONNXIFI_LOG_LEVEL_DEBUG */ #define ONNXIFI_BACKEND_PROPERTY_LOG_LEVEL 2 /** * CUDA stream to be used by the backend. * CUDA stream must be created on the CUDA device used by the ONNXIFI backend. * Users can query which CUDA device is used by the ONNXIFI backend by calling * onnxGetBackendInfo with ONNXIFI_BACKEND_CUDA_INDEX info type. * * If this property is not specified during initialization, the backend can * create a new CUDA stream for the device, or use a default CUDA stream. * * Possible values: cudaStream_t or CUstream object, cast to uint64_t. */ #define ONNXIFI_BACKEND_CUDA_STREAM 4 /** * OpenCL context to be used by the backend. * The context must be created with the OpenCL device ID and the OpenCL platform * ID used by the ONNXIFI backend. Users can query which OpenCL device ID and * OpenCL platform ID are used by the ONNXIFI backend by calling * onnxGetBackendInfo with ONNXIFI_BACKEND_OPENCL_PLATFORM_ID * and ONNXIFI_BACKEND_OPENCL_DEVICE_ID info types. * * If this property is not specified during initialization, the backend will * create a new OpenCL context for the device. * * Possible values: cl_context object, cast to uint64_t. */ #define ONNXIFI_BACKEND_OPENCL_CONTEXT 8 /** * Terminates the list of auxiliary graph initialization properties passed to * onnxInitGraph. */ #define ONNXIFI_GRAPH_PROPERTY_NONE 0 /** * Optimize graph representation and compilation for highest throughput. */ #define ONNXIFI_OPTIMIZATION_HIGH_THROUGHPUT 0 /** * Optimize graph representation and compilation for lowest latency. */ #define ONNXIFI_OPTIMIZATION_LOW_LATENCY 1 /** * Optimize graph representation and compilation for lowest power consumption. */ #define ONNXIFI_OPTIMIZATION_LOW_POWER 2 /** * Optimize graph representation and compilation for lowest delay until first * result. */ #define ONNXIFI_OPTIMIZATION_LOW_DELAY 3 /** * Log events which caused a failure in an ONNXIFI function call. */ #define ONNXIFI_LOG_LEVEL_ERROR 4 /** * Log events in ONNXIFI_LOG_LEVEL_ERROR and events which caused * a performance, accuracy, or quality of service degradation in a backend. * Enabling this logging level SHOULD NOT have a measurable effect on * performance. */ #define ONNXIFI_LOG_LEVEL_WARNING 3 /** * Log events in ONNXIFI_LOG_LEVEL_WARNING and high-level status information * about operation of a backend. Enabling this logging level MAY cause a small * degradation in performance. */ #define ONNXIFI_LOG_LEVEL_INFO 2 /** * Log events in ONNXIFI_LOG_LEVEL_INFO and detailed status information about * operations of a backend. Enabling this logging level MAY cause a serious * degradation in performance. */ #define ONNXIFI_LOG_LEVEL_DEBUG 1 /** * Tag for version 1 of tensor descriptor structure (onnxTensorDescriptorV1). * * The tag is unique for this version. If ONNXIFI introduce a new version of * the tensor descriptor structure in the future, it will get a new tag value. */ #define ONNXIFI_TAG_TENSOR_DESCRIPTOR_V1 0x43DFBF69 typedef struct onnxTensorDescriptorV1 { /** * 32-bit tag needed to distinguish different versions of a tensor descriptor * structure. In the onnxTensorDescriptorV1 structure, the tag MUST be set to * ONNXIFI_TAG_TENSOR_DESCRIPTOR_V1. If ONNXIFI introduce a new version of the * tensor descriptor structure in the future, it WILL have 32-bit tag with a * different value as the first member of the structure. * * ONNXIFI implementations MUST validate tag before accessing any other * members of the structure. */ int32_t tag; /** * Name of the blob corresponding to this tensor in the ONNX model. The name * must exactly match the ValueInfoProto.name of one of the * ModelProto.graph.input or ModelProto.graph.output */ const char* name; /** * Data type of the elements in the tensor. * * Possible values: * ONNXIFI_DATATYPE_FLOAT16 * ONNXIFI_DATATYPE_FLOAT32 * ONNXIFI_DATATYPE_FLOAT64 * ONNXIFI_DATATYPE_INT8 * ONNXIFI_DATATYPE_INT16 * ONNXIFI_DATATYPE_INT32 * ONNXIFI_DATATYPE_INT64 * ONNXIFI_DATATYPE_UINT8 * ONNXIFI_DATATYPE_UINT16 * ONNXIFI_DATATYPE_UINT32 * ONNXIFI_DATATYPE_UINT64 * ONNXIFI_DATATYPE_COMPLEX64 * ONNXIFI_DATATYPE_COMPLEX128 */ onnxEnum dataType; /** * Type of memory that stores the tensor. * * ONNXIFI_MEMORY_TYPE_CPU memory type is always supported by the backend, but * other memory types are optional. The use MUST call onnxGetBackendInfo with * ONNXIFI_BACKEND_MEMORY_TYPES to check if a particular memory type is * supported before using it. * * If the memory type is different than ONNXIFI_MEMORY_TYPE_CPU, it must be * allocated on the same device as the backend. * * Possible values: * ONNXIFI_MEMORY_TYPE_CPU (always supported) * ONNXIFI_MEMORY_TYPE_CUDA_BUFFER (support is optional) * ONNXIFI_MEMORY_TYPE_OPENCL_BUFFER (support is optional) * ONNXIFI_MEMORY_TYPE_OPENGLES_TEXTURE_2D (support is optional) * ONNXIFI_MEMORY_TYPE_D3D_RESOURCE (support is optional) */ onnxEnum memoryType; /** * Number of dimensions in the tensor. * For a scalar, the number of dimensions is 0. */ uint32_t dimensions; /** * Dimensions of the tensor. * For a scalar, this pointer can be NULL. */ const uint64_t* shape; /** * Pointers to tensor data. * * Interpretation depends on memoryType: * - ONNXIFI_MEMORY_TYPE_CPU: buffer is a valid pointer to CPU memory. * - ONNXIFI_MEMORY_TYPE_CUDA_BUFFER: buffer is a valid pointer to CUDA * device memory, allocated via cudaMalloc or cuMalloc. CUDA device memory * must be allocated on the same device as the backend. * - ONNXIFI_MEMORY_TYPE_OPENCL_BUFFER: buffer is a cl_mem handle for an * OpenCL buffer or a sub-buffer. cl_mem object must be allocated on the * same OpenCL context as the backend. The context must be specified via * ONNXIFI_BACKEND_OPENCL_CONTEXT initialization property to * onnxInitBackend. * - ONNXIFI_MEMORY_TYPE_OPENGLES_TEXTURE_2D: buffer is a name of a 2D * texture created by glGenTextures. The texture must be allocated on the * same device as the backend. * - ONNXIFI_MEMORY_TYPE_D3D_RESOURCE: TBD */ onnxPointer buffer; } onnxTensorDescriptorV1; /** * Synchronization using ONNXIFI event object (onnxEvent). */ #define ONNXIFI_SYNCHRONIZATION_EVENT 0 /** * Implicit synchronization of inputs and outputs access with the caller. * The details are backend-specific, and may involve extra parameters passed * during backend initialization. * * Examples: * - CUDA-based backends could implicitly synchronize with the caller through * the use of the same CUDA stream. * - OpenCL-based backends could implicitly synchronize with the caller through * the use of the same in-order OpenCL command queue. */ #define ONNXIFI_SYNCHRONIZATION_IMPLICIT 2 /** * Tag for version 1 of memory fence structure (onnxMemoryFenceV1). * * The tag is unique for this version. If ONNXIFI introduce a new version of * the memory fence structure in the future, it will get a new tag value. */ #define ONNXIFI_TAG_MEMORY_FENCE_V1 0x23E08AAB typedef struct onnxMemoryFenceV1 { /** * 32-bit tag needed to distinguish different versions of a memory fence * structure. In the onnxMemoryFenceV1 structure, the tag MUST be set to * ONNXIFI_TAG_MEMORY_FENCE_V1. If ONNXIFI introduce a new version of the * memory fence structure in the future, it WILL have 32-bit tag with a * different value as the first member of the structure. * * ONNXIFI implementations MUST validate tag before accessing any other * members of the structure. */ int32_t tag; /** * Type of memory synchronization primitive. * * Possible values: * ONNXIFI_SYNCHRONIZATION_EVENT (onnxEvent, always supported) * ONNXIFI_SYNCHRONIZATION_IMPLICIT */ onnxEnum type; union { /** * Handle for a single-shot ONNXIFI event used as a synchronization * primitive. Event for the input fence must be created by the caller to * onnxRunGraph. Event for the output fence is created by implementation of * onnxRunGraph, and stored into the output memory fence structure before * onnxRunGraph returns. */ onnxEvent event; }; } onnxMemoryFenceV1; /* Function pointer declarations for dynamic loading */ typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxGetBackendIDsFunction)( onnxBackendID* backendIDs, size_t* numBackends); typedef onnxStatus (ONNXIFI_ABI* onnxReleaseBackendIDFunction)( onnxBackendID backendID); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxGetBackendInfoFunction)( onnxBackendID backendID, onnxBackendInfo infoType, void* infoValue, size_t* infoValueSize); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxGetBackendCompatibilityFunction)( onnxBackendID backendID, size_t onnxModelSize, const void* onnxModel); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxInitBackendFunction)( onnxBackendID backendID, const uint64_t* auxPropertiesList, onnxBackend* backend); typedef onnxStatus (ONNXIFI_ABI* onnxReleaseBackendFunction)( onnxBackend backend); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxInitEventFunction)( onnxBackend backend, onnxEvent* event); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxSignalEventFunction)( onnxEvent event); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxGetEventStateFunction)( onnxEvent event, onnxEventState* state); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxWaitEventFunction)( onnxEvent event); typedef onnxStatus (ONNXIFI_ABI* onnxReleaseEventFunction)( onnxEvent event); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxInitGraphFunction)( onnxBackend backend, const uint64_t* auxPropertiesList, size_t onnxModelSize, const void* onnxModel, uint32_t weightsCount, const onnxTensorDescriptorV1* weightDescriptors, onnxGraph* graph); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxSetGraphIOFunction)( onnxGraph graph, uint32_t inputsCount, const onnxTensorDescriptorV1* inputDescriptors, uint32_t outputsCount, const onnxTensorDescriptorV1* outputDescriptors); typedef ONNXIFI_CHECK_RESULT onnxStatus (ONNXIFI_ABI* onnxRunGraphFunction)( onnxGraph graph, const onnxMemoryFenceV1* inputFence, onnxMemoryFenceV1* outputFence); typedef onnxStatus (ONNXIFI_ABI* onnxReleaseGraphFunction)( onnxGraph graph); /** * Get stable IDs of available backends on the system. * * ONNXIFI backend is a combination of software layer and hardware device used * to run an ONNX graph. The same software layer may expose multiple backends * (e.g. one ONNXIFI backend for each GPU in the system, or one ONNXIFI backend * for GPU and another for CPU, both implemented in the same software). Backends * implemented in the same software, but targeting different devices (e.g. * "MyNN" for CPU and "MyNN" for GPU) have different backend IDs. * * Note that some (hot-pluggable) backends can be connected and disconnected at * any time, and thus subsequent calls to this function may return different * number or set of backend IDs. The returned IDs, however, stay valid even if * the hardware device used by the backend disconnects from the system. * * To avoid resource leak, the backend ID MUST be released through a call to * onnxReleaseBackendID when it is no longer needed. * * @param backendIDs[out] - pointer to the memory location where the backend IDs * will be returned. If the pointer is NULL, it is * ignored, and the function returns only the number * of backend IDs through numBackendIDs pointer. * @param numBackendIDs[in,out] - pointer to a variable specifying number of * available backends. On function entry, the * variable MUST contain the capacity, in number * of backend IDs, of the memory buffer specified * by backendIDs. For successful completion, this * capacity must be at least as large as the * number of available backends. If the function * completes with either ONNXIFI_STATUS_SUCCESS * or ONNXIFI_STATUS_FALLBACK status codes, the * number of backend IDs written into backendIDs * buffer is stored in the variable specified by * this pointer. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded, and backend IDs * are stored in the location specified by * backendIDs, and the number of the backends * is stored in the location specified by * numBackends. * @retval ONNXIFI_STATUS_FALLBACK The function call completed, but the * backend IDs were not stored in the * location specified by backendIDs, either * because it is NULL, or because the size of * the memory buffer is insufficient to store * all available backend IDs. The number of * available backends is stored in the * location specified by numBackends. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * numBackends is NULL. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed because the * system failed to allocate memory * to store backend ID information. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxGetBackendIDs( onnxBackendID* backendIDs, size_t* numBackends); /** * Deinitialize ONNXIFI backend IDs and release associated resources. * * The user MUST deinitialize all objects created with this backend ID * (onnxBackend, onnxGraph, onnxEvent) before calling this function to * deinitialize the backend ID. * * @param backendID - backend ID returned by onnxGetBackendIDs. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the resources * associated to the backend ID were released to * the operating system. * @retval ONNXIFI_STATUS_INVALID_ID The function call failed because backendID * is not an ONNXIFI backend ID. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseBackendID( onnxBackendID backendID); /** * Query high-level information about the backend and its target device. * * ONNXIFI backend is a combination of software layer and hardware device used * to run an ONNX graph. The same software layer may expose multiple backends * (e.g. one ONNXIFI backend for each GPU in the system, or one ONNXIFI backend * for GPU and another for CPU, both implemented in the same software). * * The content, data type, and availability of information provided by this * function depends on infoType value as specified below: * * infoType value data type support * ONNXIFI_BACKEND_ONNXIFI_VERSION uint64_t required * ONNXIFI_BACKEND_NAME char[] required * ONNXIFI_BACKEND_VENDOR char[] required * ONNXIFI_BACKEND_VERSION char[] required * ONNXIFI_BACKEND_EXTENSIONS char[] required * ONNXIFI_BACKEND_DEVICE char[] required * ONNXIFI_BACKEND_DEVICE_TYPE onnxEnum required * ONNXIFI_BACKEND_ONNX_IR_VERSION char[] required * ONNXIFI_BACKEND_OPSET_VERSION char[] required * ONNXIFI_BACKEND_CAPABILITIES onnxBitfield required * ONNXIFI_BACKEND_INIT_PROPERTIES onnxBitfield required * ONNXIFI_BACKEND_MEMORY_TYPES onnxBitfield required * ONNXIFI_BACKEND_GRAPH_INIT_PROPERTIES onnxBitfield required * ONNXIFI_BACKEND_SYNCHRONIZATION_TYPES onnxBitfield required * ONNXIFI_BACKEND_MEMORY_SIZE uint64_t required * ONNXIFI_BACKEND_MAX_GRAPH_SIZE uint64_t required * ONNXIFI_BACKEND_MAX_GRAPH_COUNT uint64_t required * ONNXIFI_BACKEND_MACS_FP32 uint64_t optional * ONNXIFI_BACKEND_MACS_FP16 uint64_t optional * ONNXIFI_BACKEND_MEMORY_BANDWIDTH uint64_t optional * ONNXIFI_BACKEND_CPU_MEMORY_READ_BANDWIDTH uint64_t optional * ONNXIFI_BACKEND_CPU_MEMORY_WRITE_BANDWIDTH uint64_t optional * ONNXIFI_BACKEND_PCI_BUS_ID uint64_t optional * ONNXIFI_BACKEND_PCI_DEVICE_ID uint64_t optional * ONNXIFI_BACKEND_PCI_DOMAIN_ID uint64_t optional * ONNXIFI_BACKEND_DIRECTX_ID LUID optional * ONNXIFI_BACKEND_CUDA_INDEX uint64_t optional * ONNXIFI_BACKEND_OPENCL_PLATFORM_ID cl_platform_id optional * ONNXIFI_BACKEND_OPENCL_DEVICE_ID cl_device_id optional * * @param backendID - ID of the backend to query. * @param infoType - type of the backend information to query. Must be one of * the ONNXIFI_BACKEND_* constants. If this value is not * supported by the backend, the function will fail with * ONNXIFI_STATUS_UNSUPPORTED_ATTRIBUTE. * @param infoValue[out] - pointer to the memory location where the backend * information value will be returned. If the pointer is * NULL, is it ignored. * @param infoValueSize[in,out] - pointer to a variable specifying size, in * bytes, of the information value. On function * entry, the variable MUST contain the size of * the memory buffer specified by infoValue. * For successful completion, this size must be * at least as large as the queried value. If the * function completes with either * ONNXIFI_STATUS_SUCCESS or * ONNXIFI_STATUS_FALLBACK status codes, the * actual size of the value queried in the call * is stored in the variable specified by this * pointer. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded, and requested * value is stored in the location specified by * infoValue, and the actual size of the * requested value is stored in the location * specified by infoValueSize. * @retval ONNXIFI_STATUS_FALLBACK The function call completed, but the * requested value was not stored in the * location specified by infoValue, either * because it is NULL, or because the size of * the memory buffer is insufficient for the * value. The actual size of the requested value * is stored in the location specified by * infoValueSize. * @retval ONNXIFI_STATUS_INVALID_ID The function call failed because backendID * is not an ONNXIFI backend ID. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * infoValueSize is NULL. * @retval ONNXIFI_STATUS_UNSUPPORTED_ATTRIBUTE The function call failed because * the value of infoType is not * supported by the backend. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxGetBackendInfo( onnxBackendID backendID, onnxBackendInfo infoType, void* infoValue, size_t* infoValueSize); /** * Query if an ONNX model graph is compatible with the backend. * * Model graph is passed as a serialized ModelProto message, where types and * dimensions of all inputs (including static weights) and outputs are specified * through ModelProto.graph.input and ModelProto.graph.output messages. If the * backend supports ONNXIFI_CAPABILITY_SYMBOLIC_SIZE_TENSORS, some of the shape * dimensions can be symbolic. If the backend supports * ONNXIFI_CAPABILITY_SYMBOLIC_BATCH_SIZE, the outer shape dimension can be * symbolic. In these cases, the validation of symbolic dimension should be * deferred until graph inputs and outputs are specified in onnxSetGraphIO. * * Commonly, the serialized ModelProto message passed to this function would * not include the static weights (ModelProto.graph.initializer is empty), and * the backend implementation MUST NOT rely on the weights to determine if the * graph is supported. * * An important use-case is a ModelProto containing only a single NodeProto in * ModelProto.graph.node, which happens when a high-level framework checks * operators one-by-one to find a connected subgraph that can be offloaded to * the backend. Backend implementations SHOULD optimize performance for this * use-case. * * @param backend - ID of the backend to query. * @param onnxModelSize - size of the serialized ONNX ModelProto message, * in bytes. * @param[in] onnxModel - pointer to serialized ONNX ModelProto message * representing the model graph. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the model * graph can efficiently run on the backend. * @retval ONNXIFI_STATUS_FALLBACK The function call succeeded and the model * graph can run on the backend through some * emulation layer with some efficiency loss. If * a backend decomposes this operator into * multiple sub-operators, it should return this * code. E.g. if a backend does not natively * support grouped or depthwise convolution, but * can execute it as multiple unit-group * convolution operators, it must returns this * code. * @retval ONNXIFI_STATUS_INVALID_ID The function call failed because backendID * is not an ONNXIFI backend ID. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * onnxModel is NULL. * @retval ONNXIFI_STATUS_INVALID_SIZE The function call failed because * onnxModelSize is 0. * @retval ONNXIFI_STATUS_INVALID_PROTOBUF The function call failed because it * couldn't parse the serialized * protobuf as an ONNX ModelProto * message. * @retval ONNXIFI_STATUS_INVALID_MODEL The function call failed because the * parsed ModelProto message does not * satisfy ONNX requirements and * constraints. * @retval ONNXIFI_STATUS_UNSUPPORTED_VERSION The function call failed because * the ONNX IR version or operator * version is not supported by the * backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_OPERATOR The function call failed because * one of the operators in the model * graph is not supported by the * backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_ATTRIBUTE The function call failed because * the backend does not support the * particular AttributeProto * values in one of the operators. * @retval ONNXIFI_STATUS_UNSUPPORTED_SHAPE The function call failed because the * backend does not support the * tensor shapes in an input or output * of one of the operators. The * problematic tensor shapes could be * directly specified through * ValueInfoProto in GraphProto.input, * GraphProto.output, or * GraphProto.value_info, through * TensorProto in * GraphProto.initializer, or inferred * from the inputs by the backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_DATATYPE The function call failed because * the backend does not support the * data types in an input or output * of one of the operators. The * problematic data types could be * directly specified through * ValueInfoProto in * GraphProto.input, * GraphProto.output, or * GraphProto.value_info, through * TensorProto in * GraphProto.initializer, or * inferred from the inputs by the * backend. * @retval ONNXIFI_STATUS_MISMATCHING_SHAPE The function call failed because * output or intermediate shapes * specified in the ONNX model graph do * not match the shapes inferred from * input shapes. * @retval ONNXIFI_STATUS_MISMATCHING_DATATYPE The function call failed because * output or intermediate data types * specified in the ONNX model graph * do not match the data types * inferred from graph inputs. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed because the * backend could not allocate enough * system memory to parse and analyze * the model graph. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxGetBackendCompatibility( onnxBackendID backendID, size_t onnxModelSize, const void* onnxModel); /** * Initialize an ONNXIFI backend. * * ONNXIFI backend is a combination of software layer and hardware device used * to run an ONNXIFI graph. The same software layer may expose multiple backends * (e.g. one ONNXIFI backend for each GPU in the system, or one ONNXIFI backend * for GPU and another for CPU, both implemented in the same software). * * @param backendID - ID of the backend to initialize. * @param[in] auxPropertiesList - optional list of backend initialization * properties, terminated by * ONNXIFI_BACKEND_PROPERTY_NONE entry. Can be * NULL or empty. * @param[out] backend - pointer to an opaque handle for the initialized ONNXIFI * backend. If the function fails, the handle is * initialized to NULL. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the backend * was successfully initialized. * @retval ONNXIFI_STATUS_INVALID_ID The function call failed because backendID * is not an ONNXIFI backend ID. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * backend pointer is NULL. * @retval ONNXIFI_STATUS_INVALID_PROPERTY The function call failed because one * of the backend initialization * property values is invalid. * @retval ONNXIFI_STATUS_UNSUPPORTED_PROPERTY The function call failed because * backend does not recognize one * of the initialization * property IDs. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed due to * insufficient system memory to * initialize backend. * @retval ONNXIFI_STATUS_NO_SYSTEM_RESOURCES The function call failed due to * insufficient non-memory system * resources (e.g. file handles) to * initialize the backend. * @retval ONNXIFI_STATUS_NO_DEVICE_MEMORY The function call failed due to * insufficient backend-specific memory * to initialize the backend. * @retval ONNXIFI_STATUS_NO_DEVICE_RESOURCES The function call failed due to * insufficient non-memory * backend-specific resources (e.g. * command queues) to initialize the * backend. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxInitBackend( onnxBackendID backendID, const uint64_t* auxPropertiesList, onnxBackend* backend); /** * Deinitialize an ONNXIFI backend and release associated resources. * * The user MUST deinitialize all objects created on this backend (onnxGraph, * onnxEvent) before calling this function to deinitialize the backend. * * @param backend - ONNXIFI backend handle created by onnxInitBackend. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the backend * resources were released to the operating * system. * @retval ONNXIFI_STATUS_INVALID_BACKEND The function call failed because * backend is not an ONNXIFI backend * handle. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseBackend( onnxBackend backend); /** * Initialize a single-shot ONNXIFI event. * * The newly created event is in non-signalled state. * * @param backend - backend handle created by onnxInitBackend. This backend * would be used to initialize the event. * @param[out] event - pointer to the opaque handle for the created ONNXIFI * event. If the function fails, the handle is initialized * to NULL. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the event * was successfully initialized. * @retval ONNXIFI_STATUS_INVALID_BACKEND The function call failed because * backend is not an ONNXIFI backend * handle. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * event pointer is NULL. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed due to * insufficient system memory to * initialize the event. * @retval ONNXIFI_STATUS_NO_SYSTEM_RESOURCES The function call failed due to * insufficient non-memory system * resources (e.g. file handles) to * initialize the event. * @retval ONNXIFI_STATUS_NO_DEVICE_MEMORY The function call failed due to * insufficient backend-specific memory * to initialize the event. * @retval ONNXIFI_STATUS_NO_DEVICE_RESOURCES The function call failed due to * insufficient non-memory * backend-specific resources (e.g. * command queues) to initialize the * event. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxInitEvent( onnxBackend backend, onnxEvent* event); /** * Change the state of an ONNXIFI event to signalled. * * @param event - event handle created by onnxInitEvent. While it is technically * possible to use this function for output memory fence event * created by onnxRunGraph, users SHOULD NOT do that. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the event * was changed to signalled state. * @retval ONNXIFI_STATUS_INVALID_EVENT The function call failed because event * is not an ONNXIFI event handle. * @retval ONNXIFI_STATUS_INVALID_STATE The function call failed because event * is already in the signalled state. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxSignalEvent( onnxEvent event); /** * Query ONNXIFI event state without blocking. * * @param event - event handle created by onnxRunGraph. While it is technically * possible to use this function to events created by * onnxInitEvent, this is not the intended use-case. * @param[out] state - pointer to the variable that will store the state of the * event. If the function fails, the variable is initialized * to ONNXIFI_EVENT_STATE_INVALID. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the state * variable was initialized to either * ONNXIFI_EVENT_STATE_SIGNALLED or * ONNXIFI_EVENT_STATE_NONSIGNALLED according * to the state of the event. * @retval ONNXIFI_STATUS_INVALID_EVENT The function call failed because event * is not an ONNXIFI event handle. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because state * pointer is NULL. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxGetEventState( onnxEvent event, onnxEventState* state); /** * Wait until an ONNXIFI event transitions to signalled state. * * @param event - event handle created by onnxRunGraph. While it is technically * possible to use this function to events created by * onnxInitEvent, this is not the intended use-case. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the function * returned because event transitioned to * signalled state. * @retval ONNXIFI_STATUS_INVALID_EVENT The function call failed because event * is not an ONNXIFI event handle. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxWaitEvent( onnxEvent event); /** * Deinitialize an ONNXIFI event and release associated resources. * * @param event - event handle created by either onnxInitEvent or onnxRunGraph. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the event * resources were released to the operating * system. * @retval ONNXIFI_STATUS_INVALID_EVENT The function call failed because event * is not an ONNXIFI event handle. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseEvent( onnxEvent event); /** * Parse an ONNXIFI graph and convert it for a particular backend. * * Model graph is passed as a serialized ModelProto message, where types and * dimensions of all inputs (including static weights) and outputs are specified * through ModelProto.graph.input and ModelProto.graph.output messages. If the * backend supports ONNXIFI_CAPABILITY_SYMBOLIC_SIZE_TENSORS, some of the shape * dimensions can be symbolic. If the backend supports * ONNXIFI_CAPABILITY_SYMBOLIC_BATCH_SIZE, the outer shape dimension can be * symbolic. In these cases, their validation should be deferred until a later * call to onnxSetGraphIO. * * Values of all static weights of the graph must be specified either in * ModelProto.graph.initializer, or through the weightDescriptors parameters, * but not through any combination of the two methods. If the caller creates the * graph on the fly, it SHOULD pass weights through weightDescriptors as it * involves less overhead. * * Blobs and operators in this graph are independent of the blobs and operators * of other graphs on the same backend. * * @param backend - backend handle created by onnxInitBackend. This backend * would be used to setup and run the model graph. * @param[in] auxPropertiesList - optional list of graph initialization * properties, terminated by * ONNXIFI_GRAPH_PROPERTY_NONE entry. Can be * NULL or empty. * @param onnxModelSize - size of the serialized ONNX ModelProto message, * in bytes. * @param[in] onnxModel - pointer to serialized ONNX ModelProto message * representing the model graph. The backend MUST not * assume that the serialized ModelProto message is * present at this address after the function returns. * @param weightsCount - number of weights specified in this function call * through tensor descriptors. Alternatively, the weights * can be specified in ModelProto.graph.initializer. * If weightsCount is non-zero, weightDescriptors must be * non-NULL. * @param[in] weightDescriptors - descriptors of static input tensors for the * graph. Elements of this array provide location * for blobs identified by ValueInfoProto.name * listed in ModelProto.graph.input of the ONNX * graph. If this parameter is non-NULL, * all static inputs must be specified through * the tensor descriptors, and the * ModelProto.graph.initilizer list must be * empty. The tensor descriptors * must use ONNXIFI_MEMORY_TYPE_CPU memory type, * and the backend must copy the values of the * tensors and all metadata, including shape, * into its own memory before the function * returns. * @param[out] graph - pointer to the opaque handle for the created ONNXIFI * graph. If the function fails, and this pointer is * non-NULL, the handle is initialized to NULL. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the model * graph was successfully initialized on the * backend. * @retval ONNXIFI_STATUS_FALLBACK The function call succeeded and the model * graph was initialized for the backend through * an emulation layer with substantial * efficiency loss. If a backend decomposes an * operator into multiple sub-operators, it * MUST return this code. E.g. if a backend * does not natively support grouped or * depthwise convolution, but can execute it as * multiple unit-group convolution operators, it * should return this code. * @retval ONNXIFI_STATUS_INVALID_BACKEND The function call failed because * backend is not an ONNXIFI backend * handle. * @retval ONNXIFI_STATUS_INVALID_PROPERTY The function call failed because one * of the graph initialization property * values is invalid. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * onnxModel or graph pointer is NULL, or * weightDescriptors pointer is NULL * while weightsCount is non-zero. * @retval ONNXIFI_STATUS_INVALID_SIZE The function call failed because * onnxModelSize is 0. * @retval ONNXIFI_STATUS_INVALID_PROTOBUF The function call failed because it * couldn't parse the serialized * protobuf as an ONNX ModelProto * message. * @retval ONNXIFI_STATUS_INVALID_MODEL The function call failed because the * parsed ModelProto message does not * satisfy ONNX requirements and * constraints. * @retval ONNXIFI_STATUS_INVALID_SHAPE The function call failed because one of * the shape dimensions in * weightDescriptors is 0. * @retval ONNXIFI_STATUS_INVALID_DATATYPE The function call failed because * one of the data types in * weightDescriptors is unknown to the * backend. * @retval ONNXIFI_STATUS_INVALID_MEMORY_TYPE The function call failed because * one of the memory types in * weightDescriptors is unknown to * the backend. * @retval ONNXIFI_STATUS_INVALID_MEMORY_LOCATION The function call failed * because one of the memory * locations in weightDescriptors * is invalid (NULL pointer). * @retval ONNXIFI_STATUS_UNSUPPORTED_PROPERTY The function call failed because * backend does not recognize one * of the graph initialization * property IDs. * @retval ONNXIFI_STATUS_UNSUPPORTED_VERSION The function call failed because * the ONNX IR version or operator * version is not supported by the * backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_OPERATOR The function call failed because * one of the operators in the model * graph is not supported by the * backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_ATTRIBUTE The function call failed because * the backend does not support the * particular AttributeProto * values in one of the operators. * @retval ONNXIFI_STATUS_UNSUPPORTED_SHAPE The function call failed because the * backend does not support the * tensor shapes in an input or * output of one of the operators. * The problematic tensor shapes could * be directly specified through * ValueInfoProto in GraphProto.input, * GraphProto.output, or & GraphProto.value_info, through * TensorProto in * GraphProto.initializer, through * weightDescriptors argument, * or inferred from the inputs by the * backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_DATATYPE The function call failed because * the backend does not support the * data types in an input or output * of one of the operators. The * problematic data types could be * directly specified through * ValueInfoProto in * GraphProto.input, * GraphProto.output, or * GraphProto.value_info, through * TensorProto in * GraphProto.initializer, through * weightDescriptors argument, * or inferred from the inputs by * the backend. * @retval ONNXIFI_STATUS_UNSUPPORTED_MEMORY_TYPE The function call failed * because one of the memory * types in weightDescriptors is * different from * ONNXIFI_MEMORY_TYPE_CPU. * @retval ONNXIFI_STATUS_MISMATCHING_SHAPE The function call failed because * the shapes specified in weight * descriptors do not match the shapes * specified in the ONNX model graph, * or output or intermediate shapes * specified in the ONNX model graph do * not match the shapes inferred from * input shapes. * @retval ONNXIFI_STATUS_MISMATCHING_DATATYPE The function call failed because * data types specified in weight * descriptors do not match the data * types specified in ONNX model * graph, or output or intermediate * data types specified in the ONNX * model graph do not match the data * types inferred from graph inputs. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed because the * backend could not allocate enough * system memory to parse, analyze, and * initialize the model graph. * @retval ONNXIFI_STATUS_NO_SYSTEM_RESOURCES The function call failed due to * insufficient non-memory system * resources (e.g. file handles) to * initialize the graph. * @retval ONNXIFI_STATUS_NO_DEVICE_MEMORY The function call failed due to * insufficient backend-specific memory * to initialize the graph. * @retval ONNXIFI_STATUS_NO_DEVICE_RESOURCES The function call failed due to * insufficient non-memory * backend-specific resources (e.g. * command queues) to initialize the * graph. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * implementation experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxInitGraph( onnxBackend backend, const uint64_t* auxPropertiesList, size_t onnxModelSize, const void* onnxModel, uint32_t weightsCount, const onnxTensorDescriptorV1* weightDescriptors, onnxGraph* graph); /** * Set locations for inputs and outputs of an ONNXIFI graph. * * The caller MUST ensure that the memory buffers specified for input and output * tensors remain accessible until all in-flight graph executions which use * specified buffer locations complete AND * - Either a next call to onnxSetGraphIO specifies different buffer locations * - Or the graph is deinitialized via onnxReleaseGraph * The caller can invalidate other data in tensor descriptors, including shape, * once the function returns. * * Calls to onnxRunGraph WILL use input and output locations specified in the * preceding onnxSetGraphIO on the same graph. Asynchronous graph executions * that were in-flight before onnxSetGraphIO call will continue to use buffer * locations that were current when these graph executions started. An ONNXIFI * implementation MAY block inside onnxSetGraphIO until all in-flight graph * executions that started before the call complete. * * If a call to onnxSetGraphIO fails, it invalidates input and output locations * for the graph, and a subsequent call to onnxRunGraph will fail with * ONNXIFI_STATUS_UNIDENTIFIED_NAME. * * @param graph - graph handle created by onnxInitGraph. * @param inputsCount - number of elements in the inputDescriptors array. * @param[in] inputDescriptors - descriptors of input tensors for the graph. * Elements of this array must provide a location * for each ValueInfoProto.name listed in * ModelProto.graph.input of the ONNX graph. * If inputsCount is non-zero, inputDescriptors * pointer must be non-NULL. * @param outputsCount - number of elements in the outputDescriptors array. * Must be greater than zero. * @param[in] outputDescriptors - descriptors of output tensors for the graph. * outputDescriptors pointer must be non-NULL. * Elements of this array must provide a location * for each ValueInfoProto.name listed in * ModelProto.graph.output of the ONNX graph. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the all graph * inputs and outputs were matched to a memory * location. * @retval ONNXIFI_STATUS_INVALID_GRAPH The function call failed because * graph is not an ONNXIFI graph handle. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * outputDescriptors pointer is NULL or * inputDescriptors pointer is NULL while * inputsCount is non-zero. * @retval ONNXIFI_STATUS_INVALID_NAME The function call failed because one of * the names in tensor descriptors doesn't * match blob name in ModelProto.graph.input * or ModelProto.graph.output, or the same * name appears in more than one tensor * descriptor. * @retval ONNXIFI_STATUS_INVALID_SHAPE The function call failed because one of * the shape dimensions is 0. * @retval ONNXIFI_STATUS_INVALID_DATATYPE The function call failed because * one of the data types in * inputDescriptors or outputDescriptors * is unknown to the backend. * @retval ONNXIFI_STATUS_INVALID_MEMORY_TYPE The function call failed because * one of the memory types in * inputDescriptors or * outputDescriptors is unknown to * the backend. * @retval ONNXIFI_STATUS_INVALID_MEMORY_LOCATION The function call failed * because one of the memory * locations in inputDescriptors * or outputDescriptors is not * valid for the specified * memory type (e.g. NULL pointer * for ONNXIFI_MEMORY_TYPE_CPU). * @retval ONNXIFI_STATUS_UNSUPPORTED_TAG The function call failed because one * of the tags in inputDescriptors or * outputDescriptors is unknown to the * backend (tag does not match * ONNXIFI_TAG_TENSOR_DESCRIPTOR_V1). * @retval ONNXIFI_STATUS_UNSUPPORTED_SHAPE The function call failed because the * backend does not support the * tensor shapes in an input or output * of one of the operators. The * problematic tensor shapes could be * directly specified through * inputDescriptors or * outputDescriptors argument, * or inferred from the inputs by the * backend. This error code can be * returned when the backend supports * variable-size inputs and outputs, * and the problematic tensor shape was * provided in the ValueInfoProto as a * symbolic variable. * @retval ONNXIFI_STATUS_UNSUPPORTED_MEMORY_TYPE The function call failed * because the backend does not * support one of the memory * types in inputDescriptors or * outputDescriptors. * @retval ONNXIFI_STATUS_UNIDENTIFIED_NAME The function call failed because one * of the ValueInfoProto.name value in * ModelProto.graph.input or * ModelProto.graph.output doesn't have * a match in the inputDescriptors or * outputDescriptors. * @retval ONNXIFI_STATUS_MISMATCHING_SHAPE The function call failed because * the shapes specified through * inputDescriptors or * outputDescriptors argument are * inconsistent with the shapes * specified in the ONNX model graph. * @retval ONNXIFI_STATUS_MISMATCHING_DATATYPE The function call failed because * data types specified through * inputDescriptors or * outputDescriptors argument are * inconsistent with the data types * specified in the ONNX model * graph. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed because the * backend could not allocate enough * system memory to parse, analyze, and * initialize the tensor locations. * @retval ONNXIFI_STATUS_NO_SYSTEM_RESOURCES The function call failed due to * insufficient non-memory system * resources (e.g. file handles) to * initialize the tensor locations. * @retval ONNXIFI_STATUS_NO_DEVICE_MEMORY The function call failed due to * insufficient backend-specific memory * to initialize the tensor locations. * @retval ONNXIFI_STATUS_NO_DEVICE_RESOURCES The function call failed due to * insufficient non-memory * backend-specific resources (e.g. * command queues) to initialize the * tensor locations. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxSetGraphIO( onnxGraph graph, uint32_t inputsCount, const onnxTensorDescriptorV1* inputDescriptors, uint32_t outputsCount, const onnxTensorDescriptorV1* outputDescriptors); /** * Asynchronously execute operations in an ONNXIFI graph using pre-specified * locations for inputs and outputs. * * This function operates asynchronously: it doesn't require that the locations * for graph inputs graph inputs hold valid values before the function is * called, and doesn't guarantee that the locations for graph outputs hold * valid values when the function returns. Instead, two synchronization * primitives are used to signal to the backend when inputs are ready to use, * and to signal to the caller when outputs are ready to use. The only * synchronization primitive that is always available is onnxEvent * (ONNXIFI_SYNCHRONIZATION_EVENT memory fence type). If a backend supports * additional types of synchronization primitives, it must indicate them in * ONNXIFI_BACKEND_SYNCHRONIZATION_TYPES information query. * * The caller must successfully specify locations of input and output tensors * for the graph through onnxSetGraphIO before calling this function. * * @param graph - graph handle created by onnxInitGraph. * @param[in] inputFence - synchronization primitive that signals when graph * inputs are ready to use by the backend. The * synchronization primitive always must be initialized * by the caller. * @param[out] outputFence - synchronization primitive that signals when graph * outputs are ready to use by the caller. The type * of the synchronization primitive always must be * initialized by the caller. The type of the * synchronization primitive determines whether it * is initialized by the user before the call or by * the backend as a result of this call. Single-shot * synchronizatiom objects are initialized as a result * of the call. Reusable synchronization objects are * generally initialized by the user prior to the * call. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the all graph * inputs and outputs were matched to a memory * location. * @retval ONNXIFI_STATUS_INVALID_POINTER The function call failed because * inputFence or outputFence pointer is * NULL. * @retval ONNXIFI_STATUS_INVALID_GRAPH The function call failed because * graph is not an ONNXIFI graph handle. * @retval ONNXIFI_STATUS_INVALID_FENCE_TYPE The function call failed because * the type of synchronization * primitive specified in inputFence * or outputFence is unknown to the * backend. * @retval ONNXIFI_STATUS_INVALID_EVENT The function call failed because * the memory synchronization primitive * specified in inputFence or outputFence * is not valid (e.g. NULL onnxEvent). * @retval ONNXIFI_STATUS_UNSUPPORTED_TAG The function call failed because a tag * in inputFence or outputFence is * unknown to the backend (tag does not * match ONNXIFI_TAG_MEMORY_FENCE_V1). * @retval ONNXIFI_STATUS_UNSUPPORTED_FENCE_TYPE The function call failed * because the backend does not * support the type of * synchronization primitive * specified in inputFence or * outputFence. * @retval ONNXIFI_STATUS_UNIDENTIFIED_NAME The function call failed because * some of the ValueInfoProto.name * value in ModelProto.graph.input or * ModelProto.graph.output were not * specified in a call to * onnxSetGraphIO. * @retval ONNXIFI_STATUS_NO_SYSTEM_MEMORY The function call failed because the * backend could not allocate enough * system memory to execute the model * graph. * @retval ONNXIFI_STATUS_NO_SYSTEM_RESOURCES The function call failed due to * insufficient non-memory system * resources (e.g. file handles) to * execute the model graph. * @retval ONNXIFI_STATUS_NO_DEVICE_MEMORY The function call failed due to * insufficient backend-specific memory * to execute the graph. * @retval ONNXIFI_STATUS_NO_DEVICE_RESOURCES The function call failed due to * insufficient non-memory * backend-specific resources (e.g. * command queues) to execute the * graph. * @retval ONNXIFI_STATUS_BACKEND_UNAVAILABLE The function call failed because * the backend was disconnected or * uninstalled from the system. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * backend experienced an unrecovered * internal error. */ ONNXIFI_PUBLIC ONNXIFI_CHECK_RESULT onnxStatus ONNXIFI_ABI onnxRunGraph( onnxGraph graph, const onnxMemoryFenceV1* inputFence, onnxMemoryFenceV1* outputFence); /** * Deinitialize an ONNXIFI graph and release associated resources. * * If there are in-flight asynchronous inference operations on this graph, * the function MUST block until all outstanding operations complete. * * @param graph - graph handle created by onnxInitGraph. * * @retval ONNXIFI_STATUS_SUCCESS The function call succeeded and the graph * resources were released to the operating * system. * @retval ONNXIFI_STATUS_INVALID_GRAPH The function call failed because graph * is not an ONNXIFI graph handle. * @retval ONNXIFI_STATUS_INTERNAL_ERROR The function call failed because the * graph backend experienced an * unrecovered internal error. */ ONNXIFI_PUBLIC onnxStatus ONNXIFI_ABI onnxReleaseGraph( onnxGraph graph); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* !defined(ONNXIFI_H) */ onnx-1.7.0/onnx/common/0000775000000000000000000000000013655345213013462 5ustar rootrootonnx-1.7.0/onnx/common/ir_pb_converter.h0000664000000000000000000000215713655345213017022 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include "onnx/common/ir.h" #include "onnx/onnx_pb.h" namespace ONNX_NAMESPACE { class ConvertError final : public std::runtime_error { public: using std::runtime_error::runtime_error; ConvertError(const std::string& message) : std::runtime_error(message) {} const char* what() const noexcept override { if (!expanded_message_.empty()) { return expanded_message_.c_str(); } return std::runtime_error::what(); } void AppendContext(const std::string& context) { expanded_message_ = MakeString( std::runtime_error::what(), "\n\n==> Context: ", context); } private: std::string expanded_message_; }; #define fail_convert(...) \ throw ConvertError(MakeString(__VA_ARGS__)); void ExportModelProto(ModelProto* p_m, const std::shared_ptr& g); std::unique_ptr ImportModelProto(const ModelProto& mp); ModelProto PrepareOutput(const ModelProto& mp_in); void assertNonNull(std::shared_ptr g); } // namespace ONNX_NAMESPACE onnx-1.7.0/onnx/common/ir.h0000664000000000000000000010225313655345213014250 0ustar rootroot// ATTENTION: The code in this file is highly EXPERIMENTAL. // Adventurous users should note that the APIs will probably change. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "onnx/string_utils.h" #include "onnx/common/array_ref.h" #include "onnx/common/assertions.h" #include "onnx/common/interned_strings.h" #include "onnx/common/graph_node_list.h" #include "onnx/common/tensor.h" #define ONNX_DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&) = delete; \ TypeName& operator=(const TypeName&) = delete namespace ONNX_NAMESPACE { // Graph represents one "function" of computation. // It uses a simple ownership model where the graph owns all the nodes inside it. // All references inside the graph are raw pointers. // Destroying the Graph will invalidate any pointers to nodes in the graph. struct Graph; // Node is the base class of the IR graph. It represents one computation // and dependencies on a list of Values. The "prim-ops", so to speak. struct Node; // A Value represents an input or output to node that is either a // Tensor or an opaque Handle object, as determined by type(). struct Value; class ResourceGuard final { std::function destructor_; bool released_; public: ResourceGuard(std::function destructor) : destructor_(std::move(destructor)) , released_(false) {} ~ResourceGuard() { if (!released_) destructor_(); } void release() { released_ = true; } }; struct Dimension final { Dimension(std::string param) : is_int(false), dim(-1), param(std::move(param)) { } Dimension(int64_t dim) : is_int(true), dim(dim) {} bool is_int; int64_t dim; std::string param; }; enum class AttributeKind : uint8_t { // float, float list, int, int list, string, string list, // tensor, tensor list, subgraph, subgraph list f, fs, i, is, s, ss, t, ts, g, gs }; static inline const char * toString(AttributeKind kind) { static constexpr const char* names[] = {"f","fs", "i", "is", "s", "ss", "t", "ts", "g", "gs"}; ONNX_ASSERT(size_t(kind) < sizeof(names) / sizeof(const char*)); return names[int(kind)]; } struct AttributeValue { AttributeValue(Symbol name) : name(name) {} using Ptr = std::unique_ptr; Symbol name; virtual AttributeKind kind() const = 0; virtual Ptr clone() const = 0; virtual ~AttributeValue() = default; }; template struct ScalarAttributeValue final : public AttributeValue { using ConstructorType = const T &; using ValueType = T; ScalarAttributeValue(Symbol name, ConstructorType value_) : AttributeValue(name), value_(value_) {} ValueType & value() { return value_; } virtual Ptr clone() const override { return Ptr(new ScalarAttributeValue(name, value_)); } virtual AttributeKind kind() const override { return Kind; } private: ValueType value_; }; template struct VectorAttributeValue final : public AttributeValue { using ConstructorType = const std::vector &&; using ValueType = std::vector; VectorAttributeValue(Symbol name, ConstructorType value_) : AttributeValue(name), value_(std::move(value_)) {} ValueType & value() { return value_; } virtual AttributeKind kind() const override { return Kind; } virtual std::unique_ptr clone() const override { auto copy = value_; return Ptr(new VectorAttributeValue(name, std::move(copy))); } private: ValueType value_; }; using FloatAttr = ScalarAttributeValue; using FloatsAttr = VectorAttributeValue; using IntAttr = ScalarAttributeValue; using IntsAttr = VectorAttributeValue; using StringAttr = ScalarAttributeValue; using StringsAttr = VectorAttributeValue; using TensorAttr = ScalarAttributeValue; using TensorsAttr = VectorAttributeValue; using GraphAttr = ScalarAttributeValue,AttributeKind::g>; using GraphsAttr = VectorAttributeValue,AttributeKind::gs>; // CRTP so that Node which inherits Attributes can be return for // method chaining e.g: // Node * n = g->create(kSelect)->set_i(kOffset,3)->set_f(kValue,3.5); // we return Derived* pointers because Nodes are normally held as pointers. template struct Attributes { Attributes() {} void copyAttributes(const Attributes & rhs) { values_.clear(); values_.reserve(rhs.values_.size()); for(auto & i : rhs.values_) { values_.push_back(i->clone()); } } bool hasAttribute(Symbol name) const { return find(name,false) != values_.end(); } AttributeKind kindOf(Symbol name) const { return (*find(name,true))->kind(); } Derived* removeAttribute(Symbol name) { values_.erase(find(name,true)); return This(); } bool hasAttributes() const { return values_.size() > 0; } // The names are returned in order, since name actually is the index. std::vector attributeNames() const { std::vector names; names.reserve(values_.size()); for(auto & a : values_) names.push_back(a->name); return names; } #define CREATE_ACCESSOR(Kind, method) \ Derived* method##_(Symbol name, Kind##Attr::ConstructorType v) { \ return set(name,std::forward(v)); \ } \ const Kind##Attr::ValueType& method(Symbol name) const { \ return get(name); \ } CREATE_ACCESSOR(Float,f) CREATE_ACCESSOR(Floats,fs) CREATE_ACCESSOR(String,s) CREATE_ACCESSOR(Strings,ss) CREATE_ACCESSOR(Int,i) CREATE_ACCESSOR(Ints,is) CREATE_ACCESSOR(Tensor,t) CREATE_ACCESSOR(Tensors,ts) CREATE_ACCESSOR(Graph,g) CREATE_ACCESSOR(Graphs,gs) #undef CREATE_ACCESSOR private: Derived* This() { return static_cast(this); } template Derived* set(Symbol name, typename T::ConstructorType v) { auto it = find(name, false); auto nv = AVPtr(new T(name, std::forward(v))); if(it == values_.end()) { values_.push_back(std::move(nv)); } else { *it = std::move(nv); } return This(); } template typename T::ValueType & get(Symbol name) const { auto it = find(name, true); T* child = static_cast(it->get()); return child->value(); } using AVPtr = AttributeValue::Ptr; // NB: For determinism, we use a vector rather than a hash map. This does // mean that lookups are O(n), so you shouldn't use Attributes to store // a big pile of messages. std::vector values_; using iterator = std::vector::iterator; iterator find(Symbol name, bool required) { auto it = std::find_if(values_.begin(), values_.end(),[&](const AVPtr & v) { return v->name == name; }); ONNX_ASSERT(!required || it != values_.end()); return it; } using const_iterator = std::vector::const_iterator; const_iterator find(Symbol name, bool required) const { auto it = std::find_if(values_.begin(), values_.end(),[&](const AVPtr & v) { return v->name == name; }); ONNX_ASSERTM(!required || it != values_.end(), "%s:%u: %s: required undefined attribute '%s'", __FILE__, __LINE__, __func__, name.toString()); return it; } }; // Each use is represented by this type, see Node::uses() // 'user' is the consumer of the value, offset is the index into // 'user's input this where the produces will be found. struct Use final { Use(Node * user, size_t offset) : user(user), offset(offset) {} Node * user; size_t offset; }; static inline bool operator==(const Use & a, const Use & b) { return a.user == b.user && a.offset == b.offset; } // the list types are intentionally simple, but we type-def // them here so if we need to change them, refactoring will be easier using node_list = std::vector; using value_list = std::vector; using use_list = std::vector; using NodeKind = Symbol; struct Value final { ONNX_DISALLOW_COPY_AND_ASSIGN(Value); Value(Node * node_, size_t offset_); private: friend struct Node; friend struct Graph; Node * node_; size_t offset_; size_t unique_ = 0; // unique id size_t stage_ = 0; // 0-forward, 1-backward, 2-double-backward,... use_list uses_; bool has_unique_name_; std::string unique_name_; int32_t elem_type_; bool has_sizes_; std::vector sizes_; public: Value* setElemType(int32_t elem_type) { elem_type_ = elem_type; return this; } int32_t elemType() const { return elem_type_; } bool has_sizes() const { return has_sizes_; } Value* setSizes(std::vector sizes) { has_sizes_ = true; sizes_ = std::move(sizes); return this; } const std::vector& sizes() const { return sizes_; } size_t unique() const { return unique_; } bool has_unique_name() const { return has_unique_name_; } std::string uniqueName() const { if(has_unique_name()) return unique_name_; return ONNX_NAMESPACE::to_string(unique()); } Value* setUniqueName(std::string name) { has_unique_name_ = true; unique_name_ = std::move(name); return this; } Value* setStage(size_t s) { stage_ = s; return this; } size_t stage() const { return stage_; } Node* node() { return node_; } size_t offset() const { return offset_; } const Node * node() const { return node_; } Graph * owningGraph(); const Graph * owningGraph() const; // TODO: make this more const correct const use_list & uses() const { return uses_; } // Replaces all uses of this node with 'newValue'. // // Given: %3 = f(%1, %2) // %4 = g(%3) // %5 = h(%3, %3) // Execute: %3.replaceAllUsesWith(%6) // Result: %3 = f(%1, %2) // %4 = g(%6) // %5 = h(%6, %6) void replaceAllUsesWith(Value * newValue); Value* copyMetadata(Value * from) { setElemType(from->elemType()); setSizes(from->sizes()); if (from->has_unique_name()) { setUniqueName(from->uniqueName()); } return this; } }; struct Node : public Attributes { ONNX_DISALLOW_COPY_AND_ASSIGN(Node); friend struct Graph; friend struct Value; friend graph_node_list; friend const_graph_node_list; friend graph_node_list_iterator; friend const_graph_node_list_iterator; private: // each node but Return/Param // is associated with exactly one place in the node list... // of the graph_ // this circular is a doubly-linked list, the Return node is used as the sentinel for the beginning and end of the list // such that the list never has null pointers // next_in_graph[0] is next pointer // next_in_graph[1] is prev pointer // using an array to allow the same iterator class for forward and reverse node lists // This list represents a topological sort Node* next_in_graph[2] = { nullptr, nullptr }; Node* & next() { return next_in_graph[kNextDirection]; } Node* & prev() { return next_in_graph[kPrevDirection]; } Node* const & next() const { return next_in_graph[kNextDirection]; } Node* const & prev() const { return next_in_graph[kPrevDirection]; } const NodeKind kind_; std::vector inputs_; std::vector outputs_; Graph* graph_; size_t stage_; bool has_name_; std::string name_; bool has_domain_; std::string domain_; bool has_doc_string_; std::string doc_string_; protected: Node(Graph * graph_, NodeKind kind_); //defined after graph public: bool has_name() { return has_name_; } const std::string& name() const { return name_; } void setName(std::string name) { has_name_ = true; name_ = std::move(name); } bool has_domain() { return has_domain_; } const std::string& domain() const { return domain_; } void setDomain(std::string domain) { has_domain_ = true; domain_ = std::move(domain); } bool has_doc_string() const { return has_doc_string_; } const std::string& docString() { return doc_string_; } void setDocString(std::string doc_string) { has_doc_string_ = true; doc_string_ = std::move(doc_string); } NodeKind kind() const { return kind_; } Graph * owningGraph() { return graph_; } const Graph * owningGraph() const { return graph_; } size_t stage() const { return stage_; } Node* setStage(size_t s) { stage_ = s; return this; } // NB: This returns an ArrayRef; that means that it will // get invalidated if you resize inputs (e.g., using addInput) // We can't return a std::vector& because there's no // way to soundly cast to std::vector (an insane // implementation of std::vector could make this representationally // different.) ArrayRef inputs() { return inputs_; } ArrayRef inputs() const { // Vectors are not convertible in const-ness of elements, but // raw pointers are. return {inputs_.data(), inputs_.size()}; } // NB: This returns an ArrayRef; that means that it will // get invalidated if you resize inputs (e.g., using addInput) // We can't return a std::vector& because there's no // way to soundly cast to std::vector (an insane // implementation of std::vector could make this representationally // different.) ArrayRef outputs() { return outputs_; } ArrayRef outputs() const { // Vectors are not convertible in const-ness of elements, but // raw pointers are. return {outputs_.data(), outputs_.size()}; } bool hasUses() const { for(auto o : outputs()) { if(o->uses().size() > 0) return true; } return false; } void replaceAllUsesWith(Node * n) { ONNX_ASSERT(outputs().size() == n->outputs().size()); size_t nOutputs = outputs().size(); for(size_t i = 0; i < nOutputs; i++) { outputs()[i]->replaceAllUsesWith(n->outputs()[i]); } } // lots of things like chunk have a single input or single output, so we have a // helper to make accessing it easier Value * input() { ONNX_ASSERT(inputs_.size() == 1); return inputs_.at(0); } Value * output() { ONNX_ASSERT(outputs_.size() == 1); return outputs_.at(0); } const Value * input() const { ONNX_ASSERT(inputs_.size() == 1); return inputs_.at(0); } // Access a particular input. This is a checked index. Value * input(size_t i) { return inputs_.at(i); } const Value * input(size_t i) const { return inputs_.at(i); } // Graphs // Note [Topological invariant] // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // We always maintain an up-to-date topological ordering of all nodes via // the next()/prev() links. All transformations to graphs must preserve // this topological ordering: for example, it is only valid to 'addInput' // with an input which is topologically before the current node. // // Usually, it is obvious whether or not topological order is maintained; // for example, if you are adding nodes to the end of the topsort, it's // impossible for them to refer to inputs that are not in the topsort. // If it is not obvious, please comment accordingly. // Add 'node' as an input to 'this' at the end of existing // arguments. Returns the added node for ease of chaining. // // Given: %3 = f(%1, %2) // Execute: %3.addInput(%4) // Result: %3 = f(%1, %2, %4) Value* addInput(Value * node) { ONNX_ASSERT(graph_ == node->owningGraph()); node->uses_.emplace_back(this, inputs_.size()); inputs_.push_back(node); return node; } // Replace the input of 'this' at position 'i' with // 'newValue', returning the old node. // // Given: %3 = f(%1, %2) // Execute: %3.replaceInput(1, %4) // Result: %3 = f(%1, %4) Value * replaceInput(size_t i, Value * newValue) { ONNX_ASSERT(newValue->owningGraph() == graph_); Value * old = dropInput(i); inputs_[i] = newValue; newValue->uses_.emplace_back(this, i); return old; } // Replace all occurrences of 'from' in the inputs of this // node with 'to'. Corresponds to llvm's replaceUsesOfWith. // // Given: %3 = f(%1, %2, %1) // Execute: %3.replaceInputWith(%1, %4) // Result: %3 = f(%4, %2, %4) void replaceInputWith(Value * from, Value * to) { ONNX_ASSERT(from->owningGraph() == graph_); ONNX_ASSERT(to->owningGraph() == graph_); size_t i = 0; for(auto input : inputs()) { if(input == from) replaceInput(i, to); i++; } } Value* addOutput() { outputs_.push_back(new Value(this, outputs_.size())); return outputs_.back(); } void eraseOutput(size_t i); // Insert unattached 'this' node after 'n' in the topological order. // Returns this (for chaining). // // Given: %3 = f(%1, %2) // %4 = g(%3) // and unattached: %5 = h(%1) // Execute: %5.insertBefore(%4) // Result: %3 = f(%1, %2) // %5 = h(%1) // %4 = g(%3) Node* insertBefore(Node * n) { ONNX_ASSERT(n->inGraphList()); insertAfter(n->prev()); return this; } // Insert unattached 'this' node after 'n' in the topological order. // Returns this (for chaining). // // Given: %3 = f(%1, %2) // %4 = g(%3) // and unattached: %5 = h(%1) // Execute: %5.insertAfter(%4) // Result: %3 = f(%1, %2) // %4 = g(%3) // %5 = h(%1) Node* insertAfter(Node * n) { ONNX_ASSERT(!inGraphList() && n->inGraphList()); Node * next = n->next(); n->next() = this; this->prev() = n; this->next() = next; next->prev() = this; return this; } // Move 'this' (already in the graph) after 'n' in the topological order. // // Given: %2 = f(%1) // %3 = g(%1) // Execute: %2.moveAfter(%3) // Result: %3 = g(%1) // %2 = f(%1) // void moveAfter(Node * n) { removeFromList(); insertAfter(n); } // Move a node 'n' (already in the graph) before 'this' in the topological order. // // Given: %2 = f(%1) // %3 = g(%1) // Execute: %3.moveBefore(%2) // Result: %3 = g(%1) // %2 = f(%1) void moveBefore(Node * n) { removeFromList(); insertBefore(n); } // Remove the input at 'i' from this node. // // WARNING: This is O(n) in the number of inputs, so avoid repeatedly calling // removeInput. // // Given: %3 = f(%1, %2) // Execute: %3.removeInput(1) // Result: %3 = f(%1) void removeInput(size_t i) { dropInput(i); // everything after this input shifts left, // so we need to update their use offsets to match for(size_t j = i+1; j < inputs_.size(); j++) { auto it = findUseForInput(j); it->offset--; } inputs_.erase(inputs_.begin() + i); } // Remove all inputs from a node. // // Given: %3 = f(%1, %2) // Execute: %3.removeAllInputs() // Result: %3 = f() void removeAllInputs() { for(size_t i = 0; i < inputs().size(); ++i) dropInput(i); inputs_.clear(); } // Check whether this node is before node n in the graph. bool isBefore(Node* n); // iterators of the node list starting at this node // useful for resuming a search starting at this node graph_node_list_iterator iterator(); graph_node_list_iterator reverseIterator(); const_graph_node_list_iterator iterator() const; const_graph_node_list_iterator reverseIterator() const; // Remove 'this' from the instruction list and deallocate it. // // Invariant: no outputs of 'this' may have any uses. // // Given: %2 = f(%1) // %3 = g(%1) // Execute: %2.destroy() // Result: %3 = g(%1) void destroy(); // Dynamically cast this node to the subclass indicated by the // template variable, returning nullptr if the cast is invalid.. // // Example usage: if(auto s = n.cast