From 3387466769f37bee5979d13265b0880ff1649815 Mon Sep 17 00:00:00 2001 From: Yufeng Li Date: Fri, 1 Mar 2024 22:03:32 +0000 Subject: [PATCH 01/23] change version to rc --- VERSION_INFO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION_INFO b/VERSION_INFO index 49ffebcaa..8c69c7123 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1.0-dev \ No newline at end of file +0.1.0.rc0 From a385421dd4c86bbb4edd3bbef8b4964836797513 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Tue, 5 Mar 2024 20:10:56 -0800 Subject: [PATCH 02/23] 0.1.0-rc.1 --- .pipelines/stages/jobs/nuget-win-packaging-job.yml | 2 +- VERSION_INFO | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index d31a5ce21..38d6af87c 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -46,7 +46,7 @@ jobs: nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop id=$(id) ` - -Prop configuration=$(build_config) ` + -Prop configuration=$(build_config) ` -Prop buildPath=$(build_dir) nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec ` -Prop version=$VERSION ` diff --git a/VERSION_INFO b/VERSION_INFO index 8c69c7123..3738566b4 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1.0.rc0 +0.1.0-rc.1 From 77c2965d0888232340325466dc235d1a61a2ec94 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Tue, 5 Mar 2024 20:27:34 -0800 Subject: [PATCH 03/23] 0.1rc1.0 --- VERSION_INFO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION_INFO b/VERSION_INFO index 3738566b4..fb69c76be 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1.0-rc.1 +0.1rc1 From 39267e9f257e96a1e483aeaa787f7efdf7febd05 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Tue, 5 Mar 2024 20:29:41 -0800 Subject: [PATCH 04/23] 0.1rc1.0 --- VERSION_INFO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION_INFO b/VERSION_INFO index fb69c76be..6bfdf0529 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1rc1 +0.1rc1 \ No newline at end of file From ed96e65cd020c904b4ef76ebc291117033ba1256 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Tue, 5 Mar 2024 20:32:10 -0800 Subject: [PATCH 05/23] Microsoft.ML.OnnxRuntimeGenAI.nuspec --- .pipelines/stages/jobs/nuget-win-packaging-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index 38d6af87c..d31a5ce21 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -46,7 +46,7 @@ jobs: nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop id=$(id) ` - -Prop configuration=$(build_config) ` + -Prop configuration=$(build_config) ` -Prop buildPath=$(build_dir) nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec ` -Prop version=$VERSION ` From 4ca64ccbf886b8dae2e163642b112cc57bd1a17a Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Tue, 5 Mar 2024 21:02:57 -0800 Subject: [PATCH 06/23] 0.1.0rc1 --- .pipelines/stages/jobs/nuget-win-packaging-job.yml | 2 +- VERSION_INFO | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index d31a5ce21..cb2d987df 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -42,7 +42,7 @@ jobs: workingDirectory: '$(Build.SourcesDirectory)\src\csharp' - powershell: | - $VERSION = Get-Content $(Build.SourcesDirectory)\VERSION_INFO + $VERSION = 0.1.0-rc1 nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop id=$(id) ` diff --git a/VERSION_INFO b/VERSION_INFO index 6bfdf0529..832d5f17a 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1rc1 \ No newline at end of file +0.1.0rc1 \ No newline at end of file From 5ab1d045aeb1fc4667cb2cdae43d68274e65abe3 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Tue, 5 Mar 2024 21:17:18 -0800 Subject: [PATCH 07/23] ${{ if eq(parameters --- .../stages/jobs/capi-win-packaging-job.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.pipelines/stages/jobs/capi-win-packaging-job.yml b/.pipelines/stages/jobs/capi-win-packaging-job.yml index 5ee245ba2..a2e2f6c34 100644 --- a/.pipelines/stages/jobs/capi-win-packaging-job.yml +++ b/.pipelines/stages/jobs/capi-win-packaging-job.yml @@ -33,10 +33,30 @@ jobs: - task: CopyFiles@2 displayName: 'Copy Onnxruntime C API library files to ArtifactStagingDirectory' + condition: succeeded() inputs: SourceFolder: '$(Build.SourcesDirectory)\ort\lib' + Contents: | + onnxruntime.dll + onnxruntime.lib + onnxruntime.pdb + TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' + - ${{ if eq(parameters.ep, 'gpu') }}: + - task: CopyFiles@2 + displayName: 'Copy Onnxruntime CUDA C API library files to ArtifactStagingDirectory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\ort\lib' + Contents: | + onnxruntime_providers_cuda.dll + onnxruntime_providers_cuda.lib + onnxruntime_providers_cuda.pdb + onnxruntime_providers_shared.dll + onnxruntime_providers_shared.lib + onnxruntime_providers_shared.pdb + TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' + - task: CopyFiles@2 displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' inputs: From e76f6c9314f785f53ec5832f899dab87eb9dd9a5 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 6 Mar 2024 10:56:43 -0800 Subject: [PATCH 08/23] rm -rf ort/lib/*tensorrt* --- .pipelines/stages/jobs/py-linux-gpu-packaging-job.yml | 1 + .pipelines/stages/jobs/py-win-gpu-packaging-job.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml b/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml index 21eaad4cb..af009c1c2 100644 --- a/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml +++ b/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml @@ -51,6 +51,7 @@ jobs: - bash: | mv onnxruntime-linux-x64-gpu-${{ parameters.ort_version }} ort + rm -rf ort/lib/*tensorrt* displayName: Rename Onnxruntime to ort workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' diff --git a/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml b/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml index 7b5d1166c..2e124c9e7 100644 --- a/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml +++ b/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml @@ -72,6 +72,7 @@ jobs: - powershell: | Rename-Item -Path onnxruntime-win-x64-gpu-${{ parameters.ort_version }} -NewName ort + Remove-Item -Path ort\lib\**tensorrt* -ErrorAction SilentlyContinue displayName: Rename Onnxruntime to ort workingDirectory: '$(Build.SourcesDirectory)' From ff0a54b7dd9d9ab57778165ed446b1feb368ee57 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 6 Mar 2024 10:58:00 -0800 Subject: [PATCH 09/23] rm -rf ort/lib/*tensorrt* --- .../stages/jobs/capi-win-packaging-job.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.pipelines/stages/jobs/capi-win-packaging-job.yml b/.pipelines/stages/jobs/capi-win-packaging-job.yml index a2e2f6c34..953340da7 100644 --- a/.pipelines/stages/jobs/capi-win-packaging-job.yml +++ b/.pipelines/stages/jobs/capi-win-packaging-job.yml @@ -36,27 +36,8 @@ jobs: condition: succeeded() inputs: SourceFolder: '$(Build.SourcesDirectory)\ort\lib' - Contents: | - onnxruntime.dll - onnxruntime.lib - onnxruntime.pdb - TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' - - ${{ if eq(parameters.ep, 'gpu') }}: - - task: CopyFiles@2 - displayName: 'Copy Onnxruntime CUDA C API library files to ArtifactStagingDirectory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\ort\lib' - Contents: | - onnxruntime_providers_cuda.dll - onnxruntime_providers_cuda.lib - onnxruntime_providers_cuda.pdb - onnxruntime_providers_shared.dll - onnxruntime_providers_shared.lib - onnxruntime_providers_shared.pdb - TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' - - task: CopyFiles@2 displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' inputs: From e39127a24b326b94b017fdc01088c1589bebb528 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 6 Mar 2024 10:59:57 -0800 Subject: [PATCH 10/23] exclude headers --- .pipelines/stages/jobs/capi-win-packaging-job.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pipelines/stages/jobs/capi-win-packaging-job.yml b/.pipelines/stages/jobs/capi-win-packaging-job.yml index 953340da7..991e57caf 100644 --- a/.pipelines/stages/jobs/capi-win-packaging-job.yml +++ b/.pipelines/stages/jobs/capi-win-packaging-job.yml @@ -38,11 +38,11 @@ jobs: SourceFolder: '$(Build.SourcesDirectory)\ort\lib' TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' - - task: CopyFiles@2 - displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\ort\include' - TargetFolder: '$(Build.ArtifactStagingDirectory)\include' +# - task: CopyFiles@2 +# displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' +# inputs: +# SourceFolder: '$(Build.SourcesDirectory)\ort\include' +# TargetFolder: '$(Build.ArtifactStagingDirectory)\include' - task: CopyFiles@2 displayName: 'Copy GenAi C API library files to ArtifactStagingDirectory' From 53f688d4240cdc6a0011164b41546193afc47560 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 6 Mar 2024 11:02:13 -0800 Subject: [PATCH 11/23] '0.1.0-rc1' --- .pipelines/stages/jobs/nuget-win-packaging-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index cb2d987df..d6bd1450d 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -42,7 +42,7 @@ jobs: workingDirectory: '$(Build.SourcesDirectory)\src\csharp' - powershell: | - $VERSION = 0.1.0-rc1 + $VERSION = '0.1.0-rc1' nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop id=$(id) ` From 596dff92e4fa58bb6bbc657ccca104e8c1def502 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 6 Mar 2024 11:57:51 -0800 Subject: [PATCH 12/23] - name: arch type: string - name: ep type: string - name: ort_version type: string - name: cuda_version type: string --- .../stages/jobs/capi-win-packaging-job.yml | 26 ++++++++++--------- .../stages/jobs/nuget-win-packaging-job.yml | 24 ++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/.pipelines/stages/jobs/capi-win-packaging-job.yml b/.pipelines/stages/jobs/capi-win-packaging-job.yml index 991e57caf..008cb9b6e 100644 --- a/.pipelines/stages/jobs/capi-win-packaging-job.yml +++ b/.pipelines/stages/jobs/capi-win-packaging-job.yml @@ -1,14 +1,16 @@ parameters: - arch: 'x64' - ort_version: '1.17.1' - ep: 'cpu' - cuda_version: '11.8' +- name: arch + type: string +- name: ep + type: string +- name: ort_version + type: string +- name: cuda_version + type: string jobs: - job: Windows_CAPI_Packaging_${{ parameters.arch }}_${{ parameters.ep }} pool: 'onnxruntime-Win-CPU-2022' variables: - - name: arch - value: ${{ parameters.arch }} - name: artifactName value: onnxruntime-genai-capi-win-${{ parameters.ep }}-${{ parameters.arch }} - name: build_dir @@ -22,7 +24,7 @@ jobs: steps: - template: steps/capi-win-build-step.yml parameters: - arch: $(arch) + arch: ${{ parameters.arch }} ort_version: ${{ parameters.ort_version }} ep: ${{ parameters.ep }} cuda_version: ${{ parameters.cuda_version }} @@ -38,11 +40,11 @@ jobs: SourceFolder: '$(Build.SourcesDirectory)\ort\lib' TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' -# - task: CopyFiles@2 -# displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' -# inputs: -# SourceFolder: '$(Build.SourcesDirectory)\ort\include' -# TargetFolder: '$(Build.ArtifactStagingDirectory)\include' + # - task: CopyFiles@2 + # displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' + # inputs: + # SourceFolder: '$(Build.SourcesDirectory)\ort\include' + # TargetFolder: '$(Build.ArtifactStagingDirectory)\include' - task: CopyFiles@2 displayName: 'Copy GenAi C API library files to ArtifactStagingDirectory' diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index d6bd1450d..f1e02b901 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -1,14 +1,20 @@ parameters: - ort_version: '1.17.1' - cuda_version: '11.8' - ep: 'cpu' - arch: 'x64' +- name: arch + type: string +- name: ep + type: string +- name: ort_version + type: string +- name: cuda_version + type: string jobs: - job: Windows_Nuget_Packaging_${{ parameters.arch }}_${{ parameters.ep }} pool: 'onnxruntime-Win-CPU-2022' variables: - name: build_config value: 'Release' + - name: artifactName + value: onnxruntime-genai-nuget-win-${{ parameters.ep }}-${{ parameters.arch }} - name: build_dir ${{ if eq(parameters.ep, 'cpu') }}: value: 'build/release/cpu_default' @@ -19,21 +25,15 @@ jobs: value: Microsoft.ML.OnnxRuntimeGenAI ${{ if eq(parameters.ep, 'gpu') }}: value: Microsoft.ML.OnnxRuntimeGenAI.Gpu - - name: arch - value: ${{ parameters.arch }} - - name: ep - value: ${{ parameters.ep }} - - name: artifactName - value: onnxruntime-genai-nuget-win-${{ parameters.ep }}-${{ parameters.arch }} timeoutInMinutes: 180 workspace: clean: all steps: - template: steps/capi-win-build-step.yml parameters: - arch: $(arch) + arch: ${{ parameters.arch }} ort_version: ${{ parameters.ort_version }} - ep: $(ep) + ep: ${{ parameters.ep }} cuda_version: ${{ parameters.cuda_version }} - bash: From 7a2066c3237888fb1c259941dfcb1ba4686f6be0 Mon Sep 17 00:00:00 2001 From: Baiju Meswani Date: Wed, 6 Mar 2024 17:06:56 -0800 Subject: [PATCH 13/23] Ask pybind to load dependencies dynamically on runtime (#169) (#170) --- src/python/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 9d923e57a..9a5601493 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -19,6 +19,9 @@ pybind11_add_module(python ${python_srcs}) target_include_directories(python PRIVATE ${ORT_HEADER_DIR}) target_link_directories(python PRIVATE ${ORT_LIB_DIR}) target_link_libraries(python PRIVATE onnxruntime-genai-static ${ONNXRUNTIME_LIB}) +if(NOT WIN32) + set_property(TARGET python APPEND_STRING PROPERTY LINK_FLAGS " -Xlinker -rpath=\\$ORIGIN") +endif() set_target_properties(python PROPERTIES OUTPUT_NAME "onnxruntime_genai") if(NOT WIN32) From e301fa42f78d342481ef8ef21688c7ee93748ad6 Mon Sep 17 00:00:00 2001 From: Yufeng Li Date: Wed, 6 Mar 2024 17:36:13 -0800 Subject: [PATCH 14/23] limit thread number to 16 (#168) --- .gitignore | 2 ++ src/models/model.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index f4a00b121..b04c23c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,10 @@ *obj/ /ort /build +/build_* /test/test_models/* /cache_models +/onnxruntime-linux-x64-* /*.csv .idea cache_dir diff --git a/src/models/model.cpp b/src/models/model.cpp index 2594d5091..50d491af6 100644 --- a/src/models/model.cpp +++ b/src/models/model.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "../generators.h" #include "../search.h" #include "model.h" @@ -202,6 +205,11 @@ ONNXTensorElementDataType SessionInfo::GetOutputDataType(const std::string& name Model::Model(std::unique_ptr config, const ProviderOptions* provider_options) : config_{std::move(config)} { session_options_ = OrtSessionOptions::Create(); + constexpr int min_thread_nums = 1; + constexpr int max_thread_nums = 16; + int num_of_cores = std::max(min_thread_nums, static_cast(std::thread::hardware_concurrency() / 2)); + session_options_->SetIntraOpNumThreads(std::min(num_of_cores, max_thread_nums)); + if (provider_options != nullptr) { #if USE_CUDA if (auto* options = std::get_if(provider_options)) { From 4d8cc2f1b1d6979a7e2f4554123bd42bbc6d506c Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Thu, 7 Mar 2024 12:34:03 -0500 Subject: [PATCH 15/23] ThirdPartyNotices (#171) This pull request mainly focuses on adding the `ThirdPartyNotices.txt` file to different parts of the codebase. Specifically, the changes involve including this file in the build artifacts, NuGet packages, and Python wheel build. Here are the most significant changes: * [`.pipelines/stages/jobs/capi-win-packaging-job.yml`](diffhunk://#diff-38b1b9a2465d43797e09697e900eba44936087f430fc3115b49209f5621b31b9R69): Added the `ThirdPartyNotices.txt` file to the list of files included in the build artifacts. * `nuget/Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec` and `nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec`: Included the `ThirdPartyNotices.txt` file in the NuGet packages. [[1]](diffhunk://#diff-01d2fd48e40c38ac82de2127f5fb24b552ed6d9716b495f3dd1cfbd0f900a87aR22) [[2]](diffhunk://#diff-3c5ea4681b1c3e5e5af7c7fc811052a2d530356e8fbfe244c5429077d36d5778R32) * [`src/python/CMakeLists.txt`](diffhunk://#diff-43c9042647d14a73e4061f77bba19ee368fd059a7e0c6086063f7646e2b2aabbR54): Added the `ThirdPartyNotices.txt` file to the Python wheel build. --- .../stages/jobs/capi-win-packaging-job.yml | 1 + ThirdPartyNotices.txt | 6533 +++++++++++++++++ ...crosoft.ML.OnnxRuntimeGenAI.Managed.nuspec | 1 + nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec | 1 + src/python/CMakeLists.txt | 1 + 5 files changed, 6537 insertions(+) create mode 100644 ThirdPartyNotices.txt diff --git a/.pipelines/stages/jobs/capi-win-packaging-job.yml b/.pipelines/stages/jobs/capi-win-packaging-job.yml index 008cb9b6e..99f797dc9 100644 --- a/.pipelines/stages/jobs/capi-win-packaging-job.yml +++ b/.pipelines/stages/jobs/capi-win-packaging-job.yml @@ -73,6 +73,7 @@ jobs: LICENSE SECURITY.md README.md + ThirdPartyNotices.txt TargetFolder: '$(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt new file mode 100644 index 000000000..e7f603e41 --- /dev/null +++ b/ThirdPartyNotices.txt @@ -0,0 +1,6533 @@ +THIRD PARTY SOFTWARE NOTICES AND INFORMATION + +Do Not Translate or Localize + +This software incorporates material from third parties. Microsoft makes certain +open source code available at http://3rdpartysource.microsoft.com, or you may +send a check or money order for US $5.00, including the product name, the open +source component name, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +USA + +Notwithstanding any other terms, you may reverse engineer this software to the +extent required to debug changes to any libraries licensed under the GNU Lesser +General Public License. + +_____ + +Intel Math Kernel Library (Intel MKL) + +Intel Simplified Software License (Version April 2018) + +Copyright (c) 2018 Intel Corporation. + +Use and Redistribution. You may use and redistribute the software (the “Software”), without modification, +provided the following conditions are met: + +* Redistributions must reproduce the above copyright notice and the following terms of use in the Software +and in the documentation and/or other materials provided with the distribution. + +* Neither the name of Intel nor the names of its suppliers may be used to endorse or promote products +derived from this Software without specific prior written permission. + +* No reverse engineering, decompilation, or disassembly of this Software is permitted. + +Limited patent license. Intel grants you a world-wide, royalty-free, non-exclusive license under patents it now +or hereafter owns or controls to make, have made, use, import, offer to sell and sell (“Utilize”) this Software, +but solely to the extent that any such patent is necessary to Utilize the Software alone. The patent license +shall not apply to any combinations which include this software. No hardware per se is licensed hereunder. + +Third party and other Intel programs. “Third Party Programs” are the files listed in the “third-party-programs.txt” +text file that is included with the Software and may include Intel programs under separate license terms. +Third Party Programs, even if included with the distribution of the Materials, are governed by +separate license terms and those license terms solely govern your use of those programs. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED. THIS SOFTWARE IS +NOT INTENDED FOR USE IN SYSTEMS OR APPLICATIONS WHERE FAILURE OF THE SOFTWARE +MAY CAUSE PERSONAL INJURY OR DEATH AND YOU AGREE THAT YOU ARE FULLY RESPONSIBLE FOR ANY +CLAIMS, COSTS, DAMAGES, EXPENSES, AND ATTORNEYS’ FEES ARISING OUT OF ANY SUCH USE, +EVEN IF ANY CLAIM ALLEGES THAT INTEL WAS NEGLIGENT REGARDING THE DESIGN OR MANUFACTURE OF +THE MATERIALS. + +LIMITATION OF LIABILITY. IN NO EVENT WILL INTEL BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. YOU AGREE TO INDEMNIFY AND HOLD INTEL HARMLESS AGAINST ANY CLAIMS +AND EXPENSES RESULTING FROM YOUR USE OR UNAUTHORIZED USE OF THE SOFTWARE. + +No support. Intel may make changes to the Software, at any time without notice, and is not obligated to +support, update or provide training for the Software. + +Termination. Intel may terminate your right to use the Software in the event of your breach of this Agreement +and you fail to cure the breach within a reasonable period of time. + +Feedback. Should you provide Intel with comments, modifications, corrections, enhancements or other input +(“Feedback”) related to the Software Intel will be free to use, disclose, reproduce, license or otherwise +distribute or exploit the Feedback in its sole discretion without any obligations or restrictions of any kind, +including without limitation, intellectual property rights or licensing obligations. + +Compliance with laws. You agree to comply with all relevant laws and regulations governing your use, +transfer, import or export (or prohibition thereof) of the Software. + +Governing law. All disputes will be governed by the laws of the United States of America and the State of +Delaware without reference to conflict of law principles and subject to the exclusive jurisdiction of the state or +federal courts sitting in the State of Delaware, and each party agrees that it submits to the personal +jurisdiction and venue of those courts and waives any objections. The United Nations Convention on +Contracts for the International Sale of Goods (1980) is specifically excluded and will not apply to the +Software. + +*Other names and brands may be claimed as the property of others. + +_____ + +protocolbuffers/protobuf + +Copyright 2008 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + +_____ + +madler/zlib + +The deflate format used by zlib was defined by Phil Katz. The deflate and +zlib specifications were written by L. Peter Deutsch. Thanks to all the +people who reported problems and suggested various improvements in zlib; they +are too numerous to cite here. + +Copyright notice: + + (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. + +_____ + +pybind/pybind11 + +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. + +_____ + +onnx +Open Neural Network Exchange + +Copyright (c) Facebook, Inc. and Microsoft Corporation. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +Eigen + +MPL v2.0 +Mozilla Public License Version 2.0 + + +================================== + +1. Definitions + +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions + +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities + +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination + +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + +_____ + +intel/dnnl + +Copyright 2016-2018 Intel Corporation + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +sub-components: + +xbyak + +Copyright (c) 2007 MITSUNARI Shigeo. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. +Neither the name of the copyright owner nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +Microsoft GSL + +Copyright (c) 2015 Microsoft Corporation. All rights reserved. + +This code is licensed under the MIT License (MIT). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +_____ + +Tensorflow + +Copyright 2018 The TensorFlow Authors. All rights reserved. + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017, The TensorFlow Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +Microsoft Cognitive Toolkit (CNTK) + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +_____ + +NumPy License + +Copyright (c) 2005, NumPy Developers + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the NumPy Developers nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +Pytorch / Caffe2 + +From PyTorch: + +Copyright (c) 2016- Facebook, Inc (Adam Paszke) +Copyright (c) 2014- Facebook, Inc (Soumith Chintala) +Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) +Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) +Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) +Copyright (c) 2011-2013 NYU (Clement Farabet) +Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston) +Copyright (c) 2006 Idiap Research Institute (Samy Bengio) +Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz) + +From Caffe2: + +Copyright (c) 2016-present, Facebook Inc. All rights reserved. + +All contributions by Facebook: +Copyright (c) 2016 Facebook Inc. + +All contributions by Google: +Copyright (c) 2015 Google Inc. +All rights reserved. + +All contributions by Yangqing Jia: +Copyright (c) 2015 Yangqing Jia +All rights reserved. + +All contributions from Caffe: +Copyright(c) 2013, 2014, 2015, the respective contributors +All rights reserved. + +All other contributions: +Copyright(c) 2015, 2016 the respective contributors +All rights reserved. + +Caffe2 uses a copyright model similar to Caffe: each contributor holds +copyright over their contributions to Caffe2. The project versioning records +all such contribution and copyright details. If a contributor wants to further +mark their specific copyright on a particular contribution, they should +indicate their copyright solely in the commit message of the change when it is +committed. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories America + and IDIAP Research Institute nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +_____ + +Caffe + +COPYRIGHT + +All contributions by the University of California: +Copyright (c) 2014-2017 The Regents of the University of California (Regents) +All rights reserved. + +All other contributions: +Copyright (c) 2014-2017, the respective contributors +All rights reserved. + +Caffe uses a shared copyright model: each contributor holds copyright over +their contributions to Caffe. The project versioning records all such +contribution and copyright details. If a contributor wants to further mark +their specific copyright on a particular contribution, they should indicate +their copyright solely in the commit message of the change when it is +committed. + +LICENSE + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +CONTRIBUTION AGREEMENT + +By contributing to the BVLC/caffe repository through pull-request, comment, +or otherwise, the contributor releases their content to the +license and copyright terms herein. + +_____ + +The LLVM Compiler Infrastructure + +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2003-2017 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +Copyrights and Licenses for Third Party Software Distributed with LLVM: +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- +Google Test llvm/utils/unittest/googletest +OpenBSD regex llvm/lib/Support/{reg*, COPYRIGHT.regex} +pyyaml tests llvm/test/YAMLParser/{*.data, LICENSE.TXT} +ARM contributions llvm/lib/Target/ARM/LICENSE.TXT +md5 contributions llvm/lib/Support/MD5.cpp llvm/include/llvm/Support/MD5.h + +_____ + +google/benchmark + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +CONTRIBUTORS + +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. +# +# Names should be added to this file as: +# Name +# +# Please keep the list sorted. + +Albert Pretorius +Arne Beer +Billy Robert O'Neal III +Chris Kennelly +Christopher Seymour +David Coeurjolly +Deniz Evrenci +Dominic Hamon +Dominik Czarnota +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Federico Ficarelli +Felix Homann +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +John Millikin +Jussi Knuuttila +Kai Wolf +Kishan Kumar +Kaito Udagawa +Lei Xu +Matt Clarkson +Maxim Vafin +Nick Hutchinson +Oleksandr Sochka +Pascal Leroy +Paul Redmond +Pierre Phaneuf +Radoslav Yovchev +Raul Marin +Ray Glover +Robert Guo +Roman Lebedev +Shuo Chen +Tobias Ulvgård +Tom Madams +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron + +AUTHORS + +# This is the official list of benchmark authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. +# +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. +# +# Please keep the list sorted. + +Albert Pretorius +Arne Beer +Carto +Christopher Seymour +David Coeurjolly +Deniz Evrenci +Dirac Research +Dominik Czarnota +Eric Fiselier +Eugene Zhuk +Evgeny Safronov +Federico Ficarelli +Felix Homann +Google Inc. +International Business Machines Corporation +Ismael Jimenez Martinez +Jern-Kuan Leong +JianXiong Zhou +Joao Paulo Magalhaes +Jussi Knuuttila +Kaito Udagawa +Kishan Kumar +Lei Xu +Matt Clarkson +Maxim Vafin +MongoDB Inc. +Nick Hutchinson +Oleksandr Sochka +Paul Redmond +Radoslav Yovchev +Roman Lebedev +Shuo Chen +Steinar H. Gunderson +Stripe, Inc. +Yixuan Qiu +Yusuke Suzuki +Zbigniew Skowron + +_____ + +HalidelR + +Copyright (c) 2016 HalideIR contributors +Copyright (c) 2012-2014 MIT CSAIL, Google Inc., and other contributors +HalideIR is derived from the Halide project. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +_____ + +Distributed Machine Learning Common Codebase + +Copyright (c) 2015 by Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +_____ + +DLPack: Open In Memory Tensor Structure + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 by Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +HowardHinnant/date + +The source code in this project is released using the MIT License. There is no +global license for the project because each file is licensed individually with +different author names and/or dates. + +If you contribute to this project, please add your name to the license of each +file you modify. If you have already contributed to this project and forgot to +add your name to the license, please feel free to submit a new P/R to add your +name to the license in each file you modified. + +For convenience, here is a copy of the MIT license found in each file except +without author names or dates: + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ + +TVM Open Deep Learning Compiler Stack + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +CONTRIBUTORS + +TVM Contributors +================ +TVM adopts the Apache style model and governs by merit. We believe that it is important to create an inclusive community where everyone can use, +contribute to, and influence the direction of the project. We actively invite contributors who have earned the merit to be part of the development community. + +See the [community structure document](http://docs.tvm.ai/contribute/community.html) for the explanation of community structure and contribution guidelines. + +## Committers +- [Tianqi Chen](https://github.com/tqchen) (PMC) +- [Thierry Moreau](http://homes.cs.washington.edu/~moreau/) +- [Ziheng Jiang](https://github.com/ZihengJiang) +- [Haichen Shen](http://homes.cs.washington.edu/~haichen/) +- [Yizhi Liu](https://github.com/yzhliu) + +## Code Owners +- [Aditya Atluri](https://github.com/adityaatluri) ROCM +- [Leyuan Wang](https://github.com/Laurawly) TOPI +- [Yuwei Hu](https://github.com/Huyuwei) TOPI +- [Zhixun Tan](https://github.com/phisiart) OpenGL/WebGL backend +- [Nick Hynes](https://github.com/nhynes) SGX and secured computing +- [Lianmin Zheng](https://github.com/merrymercy) AutoTVM + +## Reviewers +- [Zhi Chen](https://github.com/zhiics) +- [Xiaoqiang Dan](https://github.com/xqdan) +- [Liangfu Chen](https://github.com/liangfu) +- [Masahiro Masuda](https://github.com/masahi) +- [Kazutaka Morita](https://github.com/kazum) +- [Tatsuya Nishiyama](https://github.com/nishi-t) +- [Pariksheet Pinjari](https://github.com/PariksheetPinjari909) +- [Jared Roesch](https://github.com/jroesch) +- [Siva](https://github.com/srkreddy1238) +- [Siju Samuel](https://github.com/siju-samuel) +- [Alex Weaver](https://github.com/alex-weaver) +- [Yao Wang](https://github.com/kevinthesun) +- [Jian Weng](https://github.com/were) +- [Eddie Yan](https://github.com/eqy) +- [Joshua Z. Zhang](https://github.com/zhreshold) + +## List of Contributors +- [Full List of Contributors](https://github.com/dmlc/tvm/graphs/contributors) + - To contributors: please add your name to the list. +- [Qiao Zhang](https://github.com/zhangqiaorjc) +- [Haolong Zhang](https://github.com/haolongzhangm) +- [Cody Hao Yu](https://github.com/comaniac) +- [Chris Nuernberger](https://github.com/cnuernber) + +_____ + +FreeBSD: getopt.c file + +Copyright (c) 1987, 1993, 1994 +The Regents of the University of California. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the University nor the names of its contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +_____ + + +google/googletest + +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +G3log : Asynchronous logger with Dynamic Sinks + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +_____ + +Scikit-learn + +Copyright (c) 2007–2018 The scikit-learn developers. +All rights reserved. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of the Scikit-learn Developers nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +_____ + +google/nsync + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +google/re2 + +Copyright (c) 2009 The RE2 Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +_____ +onnx/onnx-tensorrt + +MIT License + +Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +Copyright (c) 2018 Open Neural Network Exchange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ +nvidia/cutlass + +Copyright (c) 2017 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +SPDX-License-Identifier: BSD-3-Clause + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ +Boost + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +_____ + +JDAI-CV/DNNLibrary + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2019] [JD.com Inc. JD AI] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +_____ + +google/flatbuffers + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +_____ + +google/glog + +Copyright (c) 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +A function gettimeofday in utilities.cc is based on + +http://www.google.com/codesearch/p?hl=en#dR3YEbitojA/COPYING&q=GetSystemTimeAsFileTime%20license:bsd + +The license of this code is: + +Copyright (c) 2003-2008, Jouni Malinen and contributors +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name(s) of the above-listed copyright holder(s) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +abseil-cpp +https://github.com/abseil/abseil-cpp + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +_____ + +microsoft/wil + +MIT License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE + +_____ + +nlohmann/json + +MIT License + +Copyright (c) 2013-2019 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ + +dcleblanc/SafeInt + +MIT License + +Copyright (c) 2018 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ +Open MPI + +3-Clause BSD License + +Most files in this release are marked with the copyrights of the +organizations who have edited them. The copyrights below are in no +particular order and generally reflect members of the Open MPI core +team who have contributed code to this release. The copyrights for +code used under license from other parties are included in the +corresponding files. + +Copyright (c) 2004-2010 The Trustees of Indiana University and Indiana + University Research and Technology + Corporation. All rights reserved. +Copyright (c) 2004-2017 The University of Tennessee and The University + of Tennessee Research Foundation. All rights + reserved. +Copyright (c) 2004-2010 High Performance Computing Center Stuttgart, + University of Stuttgart. All rights reserved. +Copyright (c) 2004-2008 The Regents of the University of California. + All rights reserved. +Copyright (c) 2006-2017 Los Alamos National Security, LLC. All rights + reserved. +Copyright (c) 2006-2017 Cisco Systems, Inc. All rights reserved. +Copyright (c) 2006-2010 Voltaire, Inc. All rights reserved. +Copyright (c) 2006-2017 Sandia National Laboratories. All rights reserved. +Copyright (c) 2006-2010 Sun Microsystems, Inc. All rights reserved. + Use is subject to license terms. +Copyright (c) 2006-2017 The University of Houston. All rights reserved. +Copyright (c) 2006-2009 Myricom, Inc. All rights reserved. +Copyright (c) 2007-2017 UT-Battelle, LLC. All rights reserved. +Copyright (c) 2007-2017 IBM Corporation. All rights reserved. +Copyright (c) 1998-2005 Forschungszentrum Juelich, Juelich Supercomputing + Centre, Federal Republic of Germany +Copyright (c) 2005-2008 ZIH, TU Dresden, Federal Republic of Germany +Copyright (c) 2007 Evergrid, Inc. All rights reserved. +Copyright (c) 2008 Chelsio, Inc. All rights reserved. +Copyright (c) 2008-2009 Institut National de Recherche en + Informatique. All rights reserved. +Copyright (c) 2007 Lawrence Livermore National Security, LLC. + All rights reserved. +Copyright (c) 2007-2017 Mellanox Technologies. All rights reserved. +Copyright (c) 2006-2010 QLogic Corporation. All rights reserved. +Copyright (c) 2008-2017 Oak Ridge National Labs. All rights reserved. +Copyright (c) 2006-2012 Oracle and/or its affiliates. All rights reserved. +Copyright (c) 2009-2015 Bull SAS. All rights reserved. +Copyright (c) 2010 ARM ltd. All rights reserved. +Copyright (c) 2016 ARM, Inc. All rights reserved. +Copyright (c) 2010-2011 Alex Brick . All rights reserved. +Copyright (c) 2012 The University of Wisconsin-La Crosse. All rights + reserved. +Copyright (c) 2013-2016 Intel, Inc. All rights reserved. +Copyright (c) 2011-2017 NVIDIA Corporation. All rights reserved. +Copyright (c) 2016 Broadcom Limited. All rights reserved. +Copyright (c) 2011-2017 Fujitsu Limited. All rights reserved. +Copyright (c) 2014-2015 Hewlett-Packard Development Company, LP. All + rights reserved. +Copyright (c) 2013-2017 Research Organization for Information Science (RIST). + All rights reserved. +Copyright (c) 2017-2018 Amazon.com, Inc. or its affiliates. All Rights + reserved. +Copyright (c) 2018 DataDirect Networks. All rights reserved. +Copyright (c) 2018-2019 Triad National Security, LLC. All rights reserved. + +$COPYRIGHT$ + +Additional copyrights may follow + +$HEADER$ + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + +- Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +The copyright holders provide no reassurances that the source code +provided does not infringe any patent, copyright, or any other +intellectual property rights of third parties. The copyright holders +disclaim any liability to any recipient for claims brought against +recipient by any third party for infringement of that parties +intellectual property rights. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +The Android Open Source Project + +Copyright (C) 2017 The Android Open Source Project +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +------ + +libprotobuf-mutator + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + ----- + + openucx/ucx + https://github.com/openucx/ucx + + Copyright (c) 2014-2015 UT-Battelle, LLC. All rights reserved. + Copyright (C) 2014-2020 Mellanox Technologies Ltd. All rights reserved. + Copyright (C) 2014-2015 The University of Houston System. All rights reserved. + Copyright (C) 2015 The University of Tennessee and The University + of Tennessee Research Foundation. All rights reserved. + Copyright (C) 2016-2020 ARM Ltd. All rights reserved. + Copyright (c) 2016 Los Alamos National Security, LLC. All rights reserved. + Copyright (C) 2016-2020 Advanced Micro Devices, Inc. All rights reserved. + Copyright (C) 2019 UChicago Argonne, LLC. All rights reserved. + Copyright (c) 2018-2020 NVIDIA CORPORATION. All rights reserved. + Copyright (C) 2020 Huawei Technologies Co., Ltd. All rights reserved. + Copyright (C) 2016-2020 Stony Brook University. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ----- + + From PyTorch: + + Copyright (c) 2016- Facebook, Inc (Adam Paszke) + Copyright (c) 2014- Facebook, Inc (Soumith Chintala) + Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) + Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) + Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) + Copyright (c) 2011-2013 NYU (Clement Farabet) + Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston) + Copyright (c) 2006 Idiap Research Institute (Samy Bengio) + Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz) + + From Caffe2: + + Copyright (c) 2016-present, Facebook Inc. All rights reserved. + + All contributions by Facebook: + Copyright (c) 2016 Facebook Inc. + + All contributions by Google: + Copyright (c) 2015 Google Inc. + All rights reserved. + + All contributions by Yangqing Jia: + Copyright (c) 2015 Yangqing Jia + All rights reserved. + + All contributions from Caffe: + Copyright(c) 2013, 2014, 2015, the respective contributors + All rights reserved. + + All other contributions: + Copyright(c) 2015, 2016 the respective contributors + All rights reserved. + + Caffe2 uses a copyright model similar to Caffe: each contributor holds + copyright over their contributions to Caffe2. The project versioning records + all such contribution and copyright details. If a contributor wants to further + mark their specific copyright on a particular contribution, they should + indicate their copyright solely in the commit message of the change when it is + committed. + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories America + and IDIAP Research Institute nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +_____ + + mpi4py + https://github.com/mpi4py/mpi4py/ + + ======================= + LICENSE: MPI for Python + ======================= + + :Author: Lisandro Dalcin + :Contact: dalcinl@gmail.com + + + Copyright (c) 2019, Lisandro Dalcin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +_____ +huggingface/transformers + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +_____ +msgpack/msgpack-python + +Copyright (C) 2008-2011 INADA Naoki + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +_____ +lanpa/tensorboardX + +MIT License + +Copyright (c) 2017 Tzu-Wei Huang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +_____ +tensorflow/tensorboard + +Copyright 2017 The TensorFlow Authors. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017, The TensorFlow Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +_____ + +cerberus + +Cerberus is a lightweight and extensible data validation library for Python. + +ISC License + +Copyright (c) 2012-2016 Nicola Iarocci. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +_____ + +MurmurHash3 + +MIT license + +https://github.com/aappleby/smhasher + +SMHasher is a test suite designed to test the distribution, collision, and +performance properties of non-cryptographic hash functions. +This is the home for the MurmurHash family of hash functions along with the +SMHasher test suite used to verify them. +SMHasher is released under the MIT license. +All MurmurHash versions are public domain software, and the author disclaims all copyright to their code. + +_____ + +gtest-ios-framework + +https://github.com/mestevens/gtest-ios-framework + +Copyright (c) 2013 Matthew Stevens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +_____ + +DLPack + +https://github.com/dmlc/dlpack + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 by Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +emsdk + +MIT/Expat license + +https://github.com/emscripten-core/emsdk + +Copyright (c) 2018 Emscripten authors (see AUTHORS in Emscripten) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------------------------------------------------------------------- + +This is the MIT/Expat Licence. For more information see: + +1. http://www.opensource.org/licenses/mit-license.php + +2. http://en.wikipedia.org/wiki/MIT_License + +_____ + +coremltools + +BSD-3-Clause License + +https://github.com/apple/coremltools + +Copyright (c) 2020, Apple Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +© 2021 GitHub, Inc. + +_____ + +react-native + +MIT License + +https://github.com/facebook/react-native + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ + +pytorch/cpuinfo + +BSD 2-Clause "Simplified" License + +https://github.com/pytorch/cpuinfo + +Copyright (c) 2019 Google LLC +Copyright (c) 2017-2018 Facebook Inc. +Copyright (C) 2012-2017 Georgia Institute of Technology +Copyright (C) 2010-2012 Marat Dukhan + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +SQLite Is Public Domain + +All of the code and documentation in SQLite has been dedicated to the public +domain by the authors. All code authors, and representatives of the companies +they work for, have signed affidavits dedicating their contributions to the +public domain and originals of those signed affidavits are stored in a firesafe +at the main offices of Hwaci. Anyone is free to copy, modify, publish, use, +compile, sell, or distribute the original SQLite code, either in source code +form or as a compiled binary, for any purpose, commercial or non-commercial, +and by any means. + +The previous paragraph applies to the deliverable code and documentation in +SQLite - those parts of the SQLite library that you actually bundle and ship +with a larger application. Some scripts used as part of the build process (for +example the "configure" scripts generated by autoconf) might fall under other +open-source licenses. Nothing from these build scripts ever reaches the final +deliverable SQLite library, however, and so the licenses associated with those +scripts should not be a factor in assessing your rights to copy and use the +SQLite library. + +All of the deliverable code in SQLite has been written from scratch. No code +has been taken from other projects or from the open internet. Every line of +code can be traced back to its original author, and all of those authors have +public domain dedications on file. So the SQLite code base is clean and is +uncontaminated with licensed code from other projects. + +_____ + +google/XNNPACK + +BSD License + +For XNNPACK software + +Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. +Copyright 2019 Google LLC + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +google/sentencepiece, https://github.com/google/sentencepiece +(included when statically linked with onnxruntime-extensions) + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +dlfcn-win32/dlfcn-win32 is licensed under the MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +_____ + +The Python Imaging Library (PIL) is + + Copyright © 1997-2011 by Secret Labs AB + Copyright © 1995-2011 by Fredrik Lundh + +Pillow is the friendly PIL fork. It is + + Copyright © 2010-2023 by Alex Clark and contributors + +Like PIL, Pillow is licensed under the open source HPND License: + +By obtaining, using, and/or copying this software and/or its associated +documentation, you agree that you have read, understood, and will comply +with the following terms and conditions: + +Permission to use, copy, modify, and distribute this software and its +associated documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appears in all copies, and that +both that copyright notice and this permission notice appear in supporting +documentation, and that the name of Secret Labs AB or the author not be +used in advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, +INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +_____ + +openssl/openssl, https://github.com/openssl/openssl + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +_____ + +Tencent/rapidjson, https://github.com/Tencent/rapidjson + +Tencent is pleased to support the open source community by making RapidJSON available. + +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + +Open Source Software Licensed Under the BSD License: +-------------------------------------------------------------------- + +The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software Licensed Under the JSON License: +-------------------------------------------------------------------- + +json.org +Copyright (c) 2002 JSON.org +All Rights Reserved. + +JSON_checker +Copyright (c) 2002 JSON.org +All Rights Reserved. + + +Terms of the JSON License: +--------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Terms of the MIT License: +-------------------------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +_____ + +boostorg/boost, https://github.com/boostorg/boost + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +_____ + +libb64/libb64, https://github.com/libb64/libb64 + +Copyright-Only Dedication (based on United States law) or Public Domain Certification + +The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived. + +_____ + +posix pthread library, https://sourceforge.net/projects/pthreads4w + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +_____ + +Triton Inference Server & Client, https://github.com/triton-inference-server + +Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of NVIDIA CORPORATION nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +microsoft/mimalloc, https://github.com/microsoft/mimalloc + +MIT License + +Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ + +TensorFlow.js + +https://github.com/tensorflow/tfjs + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +—— + +curl/curl + +https://github.com/curl + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (C) Daniel Stenberg, , and many +contributors, see the THANKS file. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. + +_____ + +Intel neural-compressor + +https://github.com/intel/neural-compressor + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + ============================================================================ + + Copyright 2016-2019 Intel Corporation + Copyright 2018 YANDEX LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This distribution includes third party software ("third party programs"). + This third party software, even if included with the distribution of + the Intel software, may be governed by separate license terms, including + without limitation, third party license terms, other Intel software license + terms, and open source software license terms. These separate license terms + govern your use of the third party programs as set forth in the + "THIRD-PARTY-PROGRAMS" file. + +_____ + +FlashAttention, https://github.com/Dao-AILab/flash-attention + +BSD 3-Clause License + +Copyright (c) 2022, the respective contributors, as shown by the AUTHORS file. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +composable_kernel + +https://github.com/ROCmSoftwarePlatform/composable_kernel + +Copyright (c) 2018- , Advanced Micro Devices, Inc. (Chao Liu, Jing Zhang) +Copyright (c) 2019- , Advanced Micro Devices, Inc. (Letao Qin, Qianfeng Zhang, Liang Huang, Shaojie Wang) +Copyright (c) 2022- , Advanced Micro Devices, Inc. (Anthony Chang, Chunyu Lai, Illia Silin, Adam Osewski, Poyen Chen, Jehandad Khan) +Copyright (c) 2019-2021, Advanced Micro Devices, Inc. (Hanwen Chang) +Copyright (c) 2019-2020, Advanced Micro Devices, Inc. (Tejash Shah) +Copyright (c) 2020 , Advanced Micro Devices, Inc. (Xiaoyan Zhou) +Copyright (c) 2021-2022, Advanced Micro Devices, Inc. (Jianfeng Yan) + +SPDX-License-Identifier: MIT +Copyright (c) 2018-2023, Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +_____ + +neural-speed + +https://github.com/intel/neural-speed + + Apache License + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + ============================================================================ + + Copyright 2016-2019 Intel Corporation + Copyright 2018 YANDEX LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + This distribution includes third party software ("third party programs"). + This third party software, even if included with the distribution of + the Intel software, may be governed by separate license terms, including + without limitation, third party license terms, other Intel software license + terms, and open source software license terms. These separate license terms + govern your use of the third party programs as set forth in the + "THIRD-PARTY-PROGRAMS" file. +_____ + +ggerganov/llama.cpp https://github.com/ggerganov/llama.cpp + +MIT License + +Copyright (c) 2023 Georgi Gerganov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/nuget/Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec b/nuget/Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec index 6631339c5..11cbcccb2 100644 --- a/nuget/Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec +++ b/nuget/Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec @@ -19,6 +19,7 @@ + diff --git a/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec b/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec index 95ccaa5f2..d0bb1c7f8 100644 --- a/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec +++ b/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec @@ -29,6 +29,7 @@ + diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 9a5601493..1a2a01703 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -51,6 +51,7 @@ if(BUILD_WHEEL) # Copy over any additional python files file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/py/" DESTINATION ${WHEEL_TARGET_NAME}/) + file(COPY "${CMAKE_SOURCE_DIR}/ThirdPartyNotices.txt" DESTINATION ${WHEEL_TARGET_NAME}/) file(GLOB onnxruntime_libs "${ORT_LIB_DIR}/${ONNXRUNTIME_FILES}") add_custom_command(TARGET python POST_BUILD From 8a8c8661fcdb1202f564556c03044a90e94eaf11 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Thu, 7 Mar 2024 13:29:50 -0500 Subject: [PATCH 16/23] Fix Nuget and CAPI ESRP (#172) (#173) Co-authored-by: Yufeng Li Co-authored-by: Baiju Meswani --- .../stages/jobs/nuget-win-packaging-job.yml | 5 +++++ .../stages/jobs/steps/capi-win-build-step.yml | 16 ++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index f1e02b901..77287a908 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -41,6 +41,11 @@ jobs: displayName: 'Build CSharp' workingDirectory: '$(Build.SourcesDirectory)\src\csharp' + - template: steps/win-esrp-dll-step.yml + parameters: + FolderPath: '$(Build.SourcesDirectory)\src\csharp\bin\Release\' + DisplayName: 'ESRP - Sign C# dlls' + - powershell: | $VERSION = '0.1.0-rc1' nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` diff --git a/.pipelines/stages/jobs/steps/capi-win-build-step.yml b/.pipelines/stages/jobs/steps/capi-win-build-step.yml index f161ba299..38b3a99f6 100644 --- a/.pipelines/stages/jobs/steps/capi-win-build-step.yml +++ b/.pipelines/stages/jobs/steps/capi-win-build-step.yml @@ -80,12 +80,16 @@ steps: condition: eq('${{ parameters.ep }}', 'cpu') workingDirectory: '$(Build.SourcesDirectory)' -- template: win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)' - DisplayName: 'ESRP - Sign Native dlls' - DoEsrp: true - Pattern: '**\*genai.dll' +- ${{ if eq(parameters.ep, 'cpu') }}: + - template: win-esrp-dll-step.yml + parameters: + FolderPath: '$(Build.SourcesDirectory)\build\release\cpu_default\Release' + DisplayName: 'ESRP - Sign Native dlls' +- ${{ else }}: + - template: win-esrp-dll-step.yml + parameters: + FolderPath: '$(Build.SourcesDirectory)\build\release\cuda_default\Release' + DisplayName: 'ESRP - Sign C++ dlls' - task: BinSkim@4 displayName: 'Run BinSkim' From d12895f6267ac7219ff28f5dd20d69babc385c35 Mon Sep 17 00:00:00 2001 From: Baiju Meswani Date: Thu, 7 Mar 2024 13:03:31 -0800 Subject: [PATCH 17/23] Include libonnxruntime*.so* in the setup.py (#174) --- src/python/setup.py.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/setup.py.in b/src/python/setup.py.in index 326b0b204..866515be1 100644 --- a/src/python/setup.py.in +++ b/src/python/setup.py.in @@ -15,7 +15,7 @@ setup( version='@VERSION_INFO@', packages=['onnxruntime_genai', 'onnxruntime_genai.models'], include_package_data=True, - package_data={'': ['*.pyd', '*.dll', '*.so']}, + package_data={'': ['*.pyd', '*.dll', '*.so*']}, install_requires=[], distclass=BinaryDistribution ) From 233a55f7110fb58b6477e4c82ccd99155eb75147 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Thu, 7 Mar 2024 17:40:23 -0800 Subject: [PATCH 18/23] rc1 to rc2 --- .pipelines/stages/jobs/nuget-win-packaging-job.yml | 2 +- VERSION_INFO | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index 77287a908..a47ae54de 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -47,7 +47,7 @@ jobs: DisplayName: 'ESRP - Sign C# dlls' - powershell: | - $VERSION = '0.1.0-rc1' + $VERSION = '0.1.0-rc2' nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop id=$(id) ` diff --git a/VERSION_INFO b/VERSION_INFO index 832d5f17a..7fe3ca750 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1.0rc1 \ No newline at end of file +0.1.0rc2 \ No newline at end of file From dd64587c1896a60d3488b16d328752625a2efd0d Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 13 Mar 2024 14:45:45 -0400 Subject: [PATCH 19/23] Cherry-pick for rel-0.1.0 RC3 (#192) cherry-pick from main --------- Co-authored-by: Yufeng Li Co-authored-by: aciddelgado <139922440+aciddelgado@users.noreply.github.com> Co-authored-by: Baiju Meswani Co-authored-by: Ryan Hill <38674843+RyanUnderhill@users.noreply.github.com> Co-authored-by: kunal-vaishnavi <115581922+kunal-vaishnavi@users.noreply.github.com> --- .github/workflows/linux-cpu-x64-build.yml | 20 +-- .gitignore | 2 + .pipelines/capi-publishing.yml | 64 ------- .pipelines/nuget-publishing.yml | 29 +-- .pipelines/pypl-publishing.yml | 32 ++-- .pipelines/stages/capi-packaging-stage.yml | 42 ----- .../stages/jobs/capi-win-packaging-job.yml | 85 --------- .../stages/jobs/nuget-linux-packaging-job.yml | 51 ++++++ .../stages/jobs/nuget-win-packaging-job.yml | 89 +++++----- .../jobs/py-linux-cpu-packaging-job.yml | 167 ------------------ .../jobs/py-linux-gpu-packaging-job.yml | 163 ----------------- .../stages/jobs/py-linux-packaging-job.yml | 67 +++++++ .../stages/jobs/py-win-cpu-packaging-job.yml | 146 --------------- .../stages/jobs/py-win-gpu-packaging-job.yml | 154 ---------------- .../stages/jobs/py-win-packaging-job.yml | 95 ++++++++++ .../stages/jobs/steps/capi-linux-step.yml | 106 +++++++++++ .../stages/jobs/steps/capi-win-build-step.yml | 98 ---------- .../stages/jobs/steps/capi-win-step.yml | 82 +++++++++ .../jobs/steps/compliant-and-cleanup-step.yml | 2 +- ...nt-governance-component-detection-step.yml | 0 .../{ => compliant}/win-esrp-dll-step.yml | 0 .../stages/jobs/steps/nuget-win-step.yml | 44 +++++ .../stages/jobs/steps/utils/capi-archive.yml | 74 ++++++++ .../stages/jobs/steps/utils/download-ort.yml | 43 +++++ .../set-nightly-build-option-variable.yml} | 0 .pipelines/stages/nuget-packaging-stage.yml | 48 ++--- .pipelines/stages/py-packaging-stage.yml | 47 +++-- VERSION_INFO | 2 +- benchmark/python/README | 2 +- benchmark/python/benchmark_e2e.py | 5 +- .../presets/CMakeLinuxClangConfigPresets.json | 4 +- .../CMakeLinuxDefaultConfigPresets.json | 25 +-- cmake/presets/CMakeLinuxGccConfigPresets.json | 66 +++---- cmake/presets/CMakeWinConfigPresets.json | 96 +++++++--- examples/{phi2 => }/c/CMakeLists.txt | 0 examples/{phi2 => }/c/README.md | 2 +- examples/{phi2 => }/c/include/.gitkeep | 0 examples/{phi2 => }/c/lib/.gitkeep | 0 examples/{phi2 => }/c/src/main.cpp | 0 examples/{phi2 => }/csharp/HelloPhi2.csproj | 0 examples/{phi2 => }/csharp/HelloPhi2.sln | 0 examples/{phi2 => }/csharp/Program.cs | 2 +- examples/{phi2 => }/csharp/README.md | 2 +- examples/gemma/README.md | 45 ----- examples/gemma/gemma-loop.py | 36 ---- examples/gemma/gemma.py | 29 --- examples/llama/README.md | 45 ----- examples/llama/llama-loop.py | 36 ---- examples/llama/llama.py | 31 ---- examples/mistral/README.md | 45 ----- examples/mistral/mistral-loop.py | 119 ------------- examples/phi2/python/phi2-streaming.py | 33 ---- examples/phi2/python/phi2.py | 32 ---- examples/{phi2 => }/python/README.md | 23 ++- examples/python/chat-e2e-example.sh | 3 + examples/python/generate-e2e-example.sh | 8 + examples/python/model-chat.py | 42 +++++ examples/python/model-generate.py | 52 ++++++ nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec | 13 +- src/config.cpp | 85 +++++++++ src/config.h | 19 ++ src/csharp/GeneratorParams.cs | 4 +- src/csharp/Model.cs | 10 +- src/csharp/NativeMethods.cs | 7 +- src/csharp/Result.cs | 2 +- src/csharp/Tokenizer.cs | 6 +- src/csharp/TokenizerStream.cs | 2 +- src/csharp/Utils.cs | 16 +- src/generators.cpp | 18 -- src/generators.h | 23 +-- src/models/decoder_only.cpp | 4 +- src/models/decoder_only.h | 2 +- src/models/gpt.cpp | 4 +- src/models/gpt.h | 2 +- src/models/model.cpp | 106 ++++++++--- src/models/model.h | 5 +- src/models/onnxruntime_api.h | 13 ++ src/models/onnxruntime_inline.h | 24 +++ src/models/position_ids.cpp | 32 +--- src/models/position_ids.h | 2 +- src/models/whisper.cpp | 4 +- src/models/whisper.h | 2 +- src/ort_genai_c.cpp | 19 +- src/ort_genai_c.h | 10 +- src/python/CMakeLists.txt | 5 + src/python/py/models/DESIGN.md | 165 +++++++++++++++++ src/python/py/models/README.md | 23 ++- src/python/py/models/builder.py | 48 +++-- src/python/python.cpp | 16 +- src/smartptrs.h | 30 ++++ test/c_api_tests.cpp | 6 +- test/csharp/TestOnnxRuntimeGenAIAPI.cs | 20 +-- test/model_tests.cpp | 17 +- test/python/test_onnxruntime_genai_api.py | 22 +-- test/python/test_onnxruntime_genai_phi2.py | 2 +- .../genai_config.json | 7 + .../past.onnx | Bin .../genai_config.json | 29 +++ .../tiny-random-gpt2-fp32-cuda/past.onnx | Bin 0 -> 578141 bytes 99 files changed, 1547 insertions(+), 1812 deletions(-) delete mode 100644 .pipelines/capi-publishing.yml delete mode 100644 .pipelines/stages/capi-packaging-stage.yml delete mode 100644 .pipelines/stages/jobs/capi-win-packaging-job.yml create mode 100644 .pipelines/stages/jobs/nuget-linux-packaging-job.yml delete mode 100644 .pipelines/stages/jobs/py-linux-cpu-packaging-job.yml delete mode 100644 .pipelines/stages/jobs/py-linux-gpu-packaging-job.yml create mode 100644 .pipelines/stages/jobs/py-linux-packaging-job.yml delete mode 100644 .pipelines/stages/jobs/py-win-cpu-packaging-job.yml delete mode 100644 .pipelines/stages/jobs/py-win-gpu-packaging-job.yml create mode 100644 .pipelines/stages/jobs/py-win-packaging-job.yml create mode 100644 .pipelines/stages/jobs/steps/capi-linux-step.yml delete mode 100644 .pipelines/stages/jobs/steps/capi-win-build-step.yml create mode 100644 .pipelines/stages/jobs/steps/capi-win-step.yml rename .pipelines/stages/jobs/steps/{ => compliant}/component-governance-component-detection-step.yml (100%) rename .pipelines/stages/jobs/steps/{ => compliant}/win-esrp-dll-step.yml (100%) create mode 100644 .pipelines/stages/jobs/steps/nuget-win-step.yml create mode 100644 .pipelines/stages/jobs/steps/utils/capi-archive.yml create mode 100644 .pipelines/stages/jobs/steps/utils/download-ort.yml rename .pipelines/stages/jobs/steps/{set-nightly-build-option-variable-step.yml => utils/set-nightly-build-option-variable.yml} (100%) rename examples/{phi2 => }/c/CMakeLists.txt (100%) rename examples/{phi2 => }/c/README.md (97%) rename examples/{phi2 => }/c/include/.gitkeep (100%) rename examples/{phi2 => }/c/lib/.gitkeep (100%) rename examples/{phi2 => }/c/src/main.cpp (100%) rename examples/{phi2 => }/csharp/HelloPhi2.csproj (100%) rename examples/{phi2 => }/csharp/HelloPhi2.sln (100%) rename examples/{phi2 => }/csharp/Program.cs (93%) rename examples/{phi2 => }/csharp/README.md (96%) delete mode 100644 examples/gemma/README.md delete mode 100644 examples/gemma/gemma-loop.py delete mode 100644 examples/gemma/gemma.py delete mode 100644 examples/llama/README.md delete mode 100644 examples/llama/llama-loop.py delete mode 100644 examples/llama/llama.py delete mode 100644 examples/mistral/README.md delete mode 100644 examples/mistral/mistral-loop.py delete mode 100644 examples/phi2/python/phi2-streaming.py delete mode 100644 examples/phi2/python/phi2.py rename examples/{phi2 => }/python/README.md (54%) create mode 100755 examples/python/chat-e2e-example.sh create mode 100755 examples/python/generate-e2e-example.sh create mode 100644 examples/python/model-chat.py create mode 100644 examples/python/model-generate.py create mode 100644 src/python/py/models/DESIGN.md rename test/test_models/hf-internal-testing/{tiny-random-gpt2-fp16 => tiny-random-gpt2-fp16-cuda}/genai_config.json (77%) rename test/test_models/hf-internal-testing/{tiny-random-gpt2-fp16 => tiny-random-gpt2-fp16-cuda}/past.onnx (100%) create mode 100644 test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/genai_config.json create mode 100644 test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/past.onnx diff --git a/.github/workflows/linux-cpu-x64-build.yml b/.github/workflows/linux-cpu-x64-build.yml index 8d491d99f..1af392e22 100644 --- a/.github/workflows/linux-cpu-x64-build.yml +++ b/.github/workflows/linux-cpu-x64-build.yml @@ -10,9 +10,6 @@ env: jobs: linux_cpu_x64: - strategy: - matrix: - compiler: [ gcc, clang ] runs-on: [ "self-hosted", "1ES.Pool=onnxruntime-genai-Ubuntu2204-AMD-CPU" ] steps: - name: Checkout OnnxRuntime GenAI repo @@ -37,12 +34,12 @@ jobs: run: | set -e -x rm -rf build - cmake --preset linux_${{ matrix.compiler }}_cpu_release - cmake --build --preset linux_${{ matrix.compiler }}_cpu_release + cmake --preset linux_gcc_cpu_release + cmake --build --preset linux_gcc_cpu_release - name: Install the python wheel and test dependencies run: | - python3 -m pip install build/${{ matrix.compiler }}_cpu/release/wheel/onnxruntime_genai*.whl + python3 -m pip install build/gcc_cpu/release/wheel/onnxruntime_genai*.whl python3 -m pip install -r test/python/requirements-nightly-cpu.txt --user - name: Get HuggingFace Token @@ -60,16 +57,9 @@ jobs: if: always() continue-on-error: true run: | - ls -l ${{ github.workspace }}/build/${{ matrix.compiler }}_cpu/release + ls -l ${{ github.workspace }}/build/gcc_cpu/release - name: Run tests run: | set -e -x - ./build/${{ matrix.compiler }}_cpu/release/test/unit_tests - - - name: Upload Build Artifacts - if: ${{ matrix.compiler == 'gcc'}} - uses: actions/upload-artifact@v3 - with: - name: onnxruntime-genai-linux-cpu-x64 - path: ${{ github.workspace }}/build/**/*.a \ No newline at end of file + ./build/gcc_cpu/release/test/unit_tests diff --git a/.gitignore b/.gitignore index b04c23c3a..60b60827f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ example-models *.onnx.data __pycache__ benchmark/python/*.csv +examples/python/genai_models +examples/python/hf_cache !test/test_models/hf-internal-testing/ !test/test_models/hf-internal-testing/tiny-random-gpt2*/*.onnx \ No newline at end of file diff --git a/.pipelines/capi-publishing.yml b/.pipelines/capi-publishing.yml deleted file mode 100644 index 1233227d0..000000000 --- a/.pipelines/capi-publishing.yml +++ /dev/null @@ -1,64 +0,0 @@ -parameters: -- name: enable_win_cpu - displayName: 'Whether Windows CPU package is built.' - type: boolean - default: true - -- name: enable_win_gpu - displayName: 'Whether Windows GPU package is built.' - type: boolean - default: true - -- name: enable_win_arm - displayName: 'Whether Windows ARM package is built.' - type: boolean - default: false - -- name: enable_linux_cpu - displayName: 'Whether Linux CPU package is built.' - type: boolean - default: false - -- name: enable_linux_gpu - displayName: 'Whether Linux GPU package is built.' - type: boolean - default: false - -- name: enable_linux_arm - displayName: 'Whether Linux ARM package is built.' - type: boolean - default: false - -- name: ort_version - displayName: 'OnnxRuntime version' - type: string - default: '1.17.1' - -- name: cuda_version - displayName: 'CUDA version' - type: string - default: '11.8' - values: - - '11.8' - - '12.2' - -resources: - repositories: - - repository: manylinux - type: Github - endpoint: Microsoft - name: pypa/manylinux - ref: 5eda9aded5462201e6310105728d33016e637ea7 - -trigger: none -stages: -- template: stages/capi-packaging-stage.yml - parameters: - enable_linux_cpu: ${{ parameters.enable_linux_cpu }} - enable_linux_gpu: ${{ parameters.enable_linux_gpu }} - enable_linux_arm: ${{ parameters.enable_linux_arm }} - enable_win_cpu: ${{ parameters.enable_win_cpu }} - enable_win_gpu: ${{ parameters.enable_win_gpu }} - enable_win_arm: ${{ parameters.enable_win_arm }} - ort_version: ${{ parameters.ort_version }} - cuda_version: ${{ parameters.cuda_version }} diff --git a/.pipelines/nuget-publishing.yml b/.pipelines/nuget-publishing.yml index 5554577b0..bb639be7c 100644 --- a/.pipelines/nuget-publishing.yml +++ b/.pipelines/nuget-publishing.yml @@ -4,30 +4,21 @@ parameters: type: boolean default: true -- name: enable_win_gpu - displayName: 'Whether Windows GPU package is built.' +- name: enable_win_cuda + displayName: 'Whether Windows CUDA package is built.' type: boolean default: true -- name: enable_win_arm - displayName: 'Whether Windows ARM package is built.' - type: boolean - default: false - - name: enable_linux_cpu displayName: 'Whether Linux CPU package is built.' type: boolean - default: false + default: true -- name: enable_linux_gpu - displayName: 'Whether Linux GPU package is built.' +- name: enable_linux_cuda + displayName: 'Whether Linux CUDA package is built.' type: boolean - default: false + default: true -- name: enable_linux_arm - displayName: 'Whether Linux ARM package is built.' - type: boolean - default: false - name: ort_version displayName: 'OnnxRuntime version' @@ -54,12 +45,10 @@ trigger: none stages: - template: stages/nuget-packaging-stage.yml parameters: - enable_linux_cpu: ${{ parameters.enable_linux_cpu }} - enable_linux_gpu: ${{ parameters.enable_linux_gpu }} - enable_linux_arm: ${{ parameters.enable_linux_arm }} enable_win_cpu: ${{ parameters.enable_win_cpu }} - enable_win_gpu: ${{ parameters.enable_win_gpu }} - enable_win_arm: ${{ parameters.enable_win_arm }} + enable_win_cuda: ${{ parameters.enable_win_cuda }} + enable_linux_cpu: ${{ parameters.enable_linux_cpu }} + enable_linux_cuda: ${{ parameters.enable_linux_cuda }} ort_version: ${{ parameters.ort_version }} cuda_version: ${{ parameters.cuda_version }} diff --git a/.pipelines/pypl-publishing.yml b/.pipelines/pypl-publishing.yml index ad8cc5629..d5cb45dca 100644 --- a/.pipelines/pypl-publishing.yml +++ b/.pipelines/pypl-publishing.yml @@ -4,35 +4,34 @@ parameters: type: boolean default: true -- name: enable_win_gpu - displayName: 'Whether Windows GPU package is built.' +- name: enable_win_cuda + displayName: 'Whether Windows CUDA package is built.' type: boolean default: true -- name: enable_win_arm - displayName: 'Whether Windows ARM package is built.' - type: boolean - default: false - - name: enable_linux_cpu displayName: 'Whether Linux CPU package is built.' type: boolean default: true -- name: enable_linux_gpu - displayName: 'Whether Linux GPU package is built.' +- name: enable_linux_cuda + displayName: 'Whether Linux CUDA package is built.' type: boolean default: true -- name: enable_linux_arm - displayName: 'Whether Linux ARM package is built.' - type: boolean - default: false - name: ort_version displayName: 'OnnxRuntime version' type: string default: '1.17.1' +- name: cuda_version + displayName: 'CUDA version' + type: string + default: '11.8' + values: + - '11.8' + - '12.2' + resources: repositories: - repository: manylinux @@ -46,9 +45,8 @@ stages: - template: stages/py-packaging-stage.yml parameters: enable_linux_cpu: ${{ parameters.enable_linux_cpu }} - enable_linux_gpu: ${{ parameters.enable_linux_gpu }} - enable_linux_arm: ${{ parameters.enable_linux_arm }} + enable_linux_cuda: ${{ parameters.enable_linux_cuda }} enable_win_cpu: ${{ parameters.enable_win_cpu }} - enable_win_gpu: ${{ parameters.enable_win_gpu }} - enable_win_arm: ${{ parameters.enable_win_arm }} + enable_win_cuda: ${{ parameters.enable_win_cuda }} ort_version: ${{ parameters.ort_version }} + cuda_version: ${{ parameters.cuda_version }} diff --git a/.pipelines/stages/capi-packaging-stage.yml b/.pipelines/stages/capi-packaging-stage.yml deleted file mode 100644 index 76afb2e91..000000000 --- a/.pipelines/stages/capi-packaging-stage.yml +++ /dev/null @@ -1,42 +0,0 @@ -parameters: -- name: enable_win_cpu - type: boolean -- name: enable_win_gpu - type: boolean -- name: enable_win_arm - type: boolean -- name: enable_linux_cpu - type: boolean -- name: enable_linux_gpu - type: boolean -- name: enable_linux_arm - type: boolean -- name: ort_version - type: string -- name: cuda_version - type: string - -stages: -- stage: capi_packaging - jobs: - - ${{ if eq(parameters.enable_win_cpu, true) }}: - - template: jobs/capi-win-packaging-job.yml - parameters: - cuda_version: ${{ parameters.cuda_version }} - ort_version: ${{ parameters.ort_version }} - arch: 'x64' - ep: 'cpu' - - ${{ if eq(parameters.enable_win_gpu, true) }}: - - template: jobs/capi-win-packaging-job.yml - parameters: - cuda_version: ${{ parameters.cuda_version }} - ort_version: ${{ parameters.ort_version }} - arch: 'x64' - ep: 'gpu' - - ${{ if eq(parameters.enable_win_arm, true) }}: - - template: jobs/capi-win-packaging-job.yml - parameters: - cuda_version: ${{ parameters.cuda_version }} - ort_version: ${{ parameters.ort_version }} - arch: 'arm64' - ep: 'cpu' diff --git a/.pipelines/stages/jobs/capi-win-packaging-job.yml b/.pipelines/stages/jobs/capi-win-packaging-job.yml deleted file mode 100644 index 99f797dc9..000000000 --- a/.pipelines/stages/jobs/capi-win-packaging-job.yml +++ /dev/null @@ -1,85 +0,0 @@ -parameters: -- name: arch - type: string -- name: ep - type: string -- name: ort_version - type: string -- name: cuda_version - type: string -jobs: -- job: Windows_CAPI_Packaging_${{ parameters.arch }}_${{ parameters.ep }} - pool: 'onnxruntime-Win-CPU-2022' - variables: - - name: artifactName - value: onnxruntime-genai-capi-win-${{ parameters.ep }}-${{ parameters.arch }} - - name: build_dir - ${{ if eq(parameters.ep, 'cpu') }}: - value: 'build\release\cpu_default' - ${{ if eq(parameters.ep, 'gpu') }}: - value: 'build\release\cuda_default' - timeoutInMinutes: 180 - workspace: - clean: all - steps: - - template: steps/capi-win-build-step.yml - parameters: - arch: ${{ parameters.arch }} - ort_version: ${{ parameters.ort_version }} - ep: ${{ parameters.ep }} - cuda_version: ${{ parameters.cuda_version }} - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse - displayName: 'List all files in the repo for ${{ parameters.arch }} ${{ parameters.ep }}' - - - task: CopyFiles@2 - displayName: 'Copy Onnxruntime C API library files to ArtifactStagingDirectory' - condition: succeeded() - inputs: - SourceFolder: '$(Build.SourcesDirectory)\ort\lib' - TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' - - # - task: CopyFiles@2 - # displayName: 'Copy Onnxruntime C API header files to ArtifactStagingDirectory' - # inputs: - # SourceFolder: '$(Build.SourcesDirectory)\ort\include' - # TargetFolder: '$(Build.ArtifactStagingDirectory)\include' - - - task: CopyFiles@2 - displayName: 'Copy GenAi C API library files to ArtifactStagingDirectory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\$(build_dir)\Release' - Contents: | - onnxruntime-genai.dll - onnxruntime-genai.lib - onnxruntime-genai.pdb - TargetFolder: '$(Build.ArtifactStagingDirectory)\lib' - - - task: CopyFiles@2 - displayName: 'Copy GenAi C API header to ArtifactStagingDirectory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\src' - Contents: | - ort_genai_c.h - TargetFolder: '$(Build.ArtifactStagingDirectory)\include' - - - task: CopyFiles@2 - displayName: 'Copy other files to ArtifactStagingDirectory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)' - Contents: | - VERSION_INFO - LICENSE - SECURITY.md - README.md - ThirdPartyNotices.txt - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai capi' - inputs: - ArtifactName: $(artifactName) - - - template: steps/compliant-and-cleanup-step.yml - diff --git a/.pipelines/stages/jobs/nuget-linux-packaging-job.yml b/.pipelines/stages/jobs/nuget-linux-packaging-job.yml new file mode 100644 index 000000000..081e8ef97 --- /dev/null +++ b/.pipelines/stages/jobs/nuget-linux-packaging-job.yml @@ -0,0 +1,51 @@ +parameters: +- name: arch + type: string +- name: ep + type: string +- name: ort_version + type: string +- name: cuda_version + type: string + default: '' + +jobs: +- job: Linux_Nuget_Packaging_${{ parameters.ep }}_${{ parameters.arch }} + pool: 'onnxruntime-Ubuntu2204-AMD-CPU' + timeoutInMinutes: 180 + variables: + - name: artifactName + value: 'onnxruntime-genai-capi-linux-${{ parameters.ep }}-${{ parameters.arch }}' + - name: ort_version + value: ${{ parameters.ort_version }} + - name: arch + value: ${{ parameters.arch }} + - name: ep + value: ${{ parameters.ep }} + - name: buildDir + value: 'build/gcc_${{ parameters.ep }}/release' + - name: ort_filename + ${{ if eq(parameters.ep, 'cpu') }}: + value: 'onnxruntime-linux-${{ parameters.arch }}-${{ parameters.ort_version }}' + ${{ else}}: + ${{if eq(parameters.cuda_version, '11.8') }}: + value: 'onnxruntime-linux-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' + ${{ else }}: + value: 'onnxruntime-linux-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' + workspace: + clean: all + steps: + - template: steps/capi-linux-step.yml + parameters: + target: 'onnxruntime-genai' + genai_src: '$(Build.SourcesDirectory)/onnxruntime-genai' + +# TODO: Add a step to build the nuget package + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime Genai capi' + inputs: + ArtifactName: $(artifactName) + + - template: steps/compliant-and-cleanup-step.yml + diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml index a47ae54de..de370fe18 100644 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ b/.pipelines/stages/jobs/nuget-win-packaging-job.yml @@ -1,75 +1,68 @@ parameters: - name: arch type: string + values: + - 'x64' + - 'arm64' - name: ep type: string + values: + - 'cpu' + - 'cuda' - name: ort_version type: string - name: cuda_version type: string + default: '' jobs: -- job: Windows_Nuget_Packaging_${{ parameters.arch }}_${{ parameters.ep }} +- job: Windows_Nuget_Packaging_${{ parameters.ep }}_${{ parameters.arch }} pool: 'onnxruntime-Win-CPU-2022' + timeoutInMinutes: 180 variables: - - name: build_config + - name: buildConfig value: 'Release' + - name: cuda_version + value: ${{ parameters.cuda_version }} + - name: ort_version + value: ${{ parameters.ort_version }} + - name: arch + value: ${{ parameters.arch }} + - name: ep + value: ${{ parameters.ep }} + - name: buildDir + value: 'build\release\${{ parameters.ep }}_default' - name: artifactName - value: onnxruntime-genai-nuget-win-${{ parameters.ep }}-${{ parameters.arch }} - - name: build_dir + value : 'onnxruntime-genai-capi-win-${{ parameters.ep }}-${{ parameters.arch }}' + - name: ort_filename ${{ if eq(parameters.ep, 'cpu') }}: - value: 'build/release/cpu_default' - ${{ if eq(parameters.ep, 'gpu') }}: - value: 'build/release/cuda_default' - - name: id + value: 'onnxruntime-win-${{ parameters.arch }}-${{ parameters.ort_version }}' + ${{ else}}: + ${{if eq(parameters.cuda_version, '11.8') }}: + value: 'onnxruntime-win-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' + ${{ else }}: + value: 'onnxruntime-win-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' + - name: genai_nuget_ext ${{ if eq(parameters.ep, 'cpu') }}: - value: Microsoft.ML.OnnxRuntimeGenAI - ${{ if eq(parameters.ep, 'gpu') }}: - value: Microsoft.ML.OnnxRuntimeGenAI.Gpu - timeoutInMinutes: 180 + value: '' + ${{ if eq(parameters.ep, 'cuda') }}: + value: '.Cuda' + - name: ort_nuget_ext + ${{ if eq(parameters.ep, 'cpu') }}: + value: '' + ${{ if eq(parameters.ep, 'cuda') }}: + value: '.Gpu' workspace: clean: all steps: - - template: steps/capi-win-build-step.yml + - template: steps/capi-win-step.yml parameters: - arch: ${{ parameters.arch }} - ort_version: ${{ parameters.ort_version }} - ep: ${{ parameters.ep }} - cuda_version: ${{ parameters.cuda_version }} + target: 'onnxruntime-genai' - - bash: - dotnet build Microsoft.ML.OnnxRuntimeGenAI.csproj -p:Configuration="$(build_config)" -p:NativeBuildOutputDir="$(build_dir)\$(build_config)" - displayName: 'Build CSharp' - workingDirectory: '$(Build.SourcesDirectory)\src\csharp' + - template: steps/nuget-win-step.yml - - template: steps/win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)\src\csharp\bin\Release\' - DisplayName: 'ESRP - Sign C# dlls' - - - powershell: | - $VERSION = '0.1.0-rc2' - nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` - -Prop version=$VERSION ` - -Prop id=$(id) ` - -Prop configuration=$(build_config) ` - -Prop buildPath=$(build_dir) - nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec ` - -Prop version=$VERSION ` - -Prop configuration=$(build_config) - displayName: 'Nuget Packaging' - workingDirectory: '$(Build.SourcesDirectory)\nuget' - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse - - task: CopyFiles@2 - displayName: 'Copy Nuget to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\nuget' - Contents: '*.nupkg' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai nuget' + displayName: 'Publish Artifact: ONNXRuntime Genai capi' inputs: ArtifactName: $(artifactName) diff --git a/.pipelines/stages/jobs/py-linux-cpu-packaging-job.yml b/.pipelines/stages/jobs/py-linux-cpu-packaging-job.yml deleted file mode 100644 index e0a19ccfb..000000000 --- a/.pipelines/stages/jobs/py-linux-cpu-packaging-job.yml +++ /dev/null @@ -1,167 +0,0 @@ -parameters: -- name: arch - type: string - default: 'x64' -- name: ort_version - type: string - default: '1.17.1' -jobs: -- job: Linux_CPU_Wheels - strategy: - matrix: - x64: - buildArch: 'x64' - machine_pool: 'onnxruntime-Ubuntu2204-AMD-CPU' - docker_arch: 'x64' - ${{ if eq(parameters.arch, 'arm64') }}: - arm64: - buildArch: 'arm64' - machine_pool: 'onnxruntime-linux-ARM64-CPU-2019' - docker_arch: 'aarch64' - timeoutInMinutes: 240 - workspace: - clean: all - pool: $(machine_pool) - variables: - # The build machine pool doesn't have dotnet, so it can't run CG. - - name: skipComponentGovernanceDetection - value: true - - steps: - - checkout: self # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/onnxruntime-genai, - clean: true - submodules: recursive - - - checkout: manylinux # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/manylinux, - submodules: false - - - template: steps/set-nightly-build-option-variable-step.yml - - - task: DownloadGitHubRelease@0 - inputs: - connection: 'GitHub - Release' - userRepository: 'microsoft/onnxruntime' - defaultVersionType: 'specificTag' - version: 'v${{ parameters.ort_version }}' - itemPattern: 'onnxruntime-linux-$(buildArch)-${{ parameters.ort_version }}*' - downloadPath: '$(Build.SourcesDirectory)' - displayName: Download ONNXRuntime - - - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '**/*.tgz' - destinationFolder: '$(Build.SourcesDirectory)/onnxruntime-genai' - cleanDestinationFolder: false - overwriteExistingFiles: true - displayName: Unzip OnnxRuntime - - - bash: | - mv onnxruntime-linux-$(buildArch)-${{ parameters.ort_version }} ort - displayName: Rename Onnxruntime to ort - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - script: | - set -e -x - az login --identity --username 63b63039-6328-442f-954b-5a64d124e5b4 - az acr login --name onnxruntimebuildcache --subscription 00c06639-6ee4-454e-8058-8d8b1703bd87 - python3 tools/ci_build/get_docker_image.py --dockerfile tools/ci_build/github/linux/docker/manylinux/Dockerfile.manylinux2_28_cpu \ - --context tools/ci_build/github/linux/docker/manylinux \ - --docker-build-args "--build-arg BUILD_UID=$( id -u )" \ - --container-registry onnxruntimebuildcache \ - --manylinux-src $(Build.SourcesDirectory)/manylinux \ - --multiple_repos \ - --repository onnxruntimecpubuild$(docker_arch) - displayName: 'Get Docker Image' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimecpubuild$(docker_arch) \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cpu_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON \ - -DPYTHON_EXECUTABLE=/opt/python/cp38-cp38/bin/python3.8 && \ - /usr/bin/cmake --build --preset linux_gcc_cpu_release" - displayName: 'Build Python Wheel 3.8' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimecpubuild$(docker_arch) \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cpu_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON \ - -DPYTHON_EXECUTABLE=/opt/python/cp39-cp39/bin/python3.9 && \ - /usr/bin/cmake --build --preset linux_gcc_cpu_release" - displayName: 'Build Python Wheel 3.9' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimecpubuild$(docker_arch) \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cpu_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON \ - -DPYTHON_EXECUTABLE=/opt/python/cp310-cp310/bin/python3.10 && \ - /usr/bin/cmake --build --preset linux_gcc_cpu_release" - displayName: 'Build Python Wheel 3.10' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimecpubuild$(docker_arch) \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cpu_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON \ - -DPYTHON_EXECUTABLE=/opt/python/cp311-cp311/bin/python3.11 && \ - /usr/bin/cmake --build --preset linux_gcc_cpu_release" - displayName: 'Build Python Wheel 3.11' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimecpubuild$(docker_arch) \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cpu_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON \ - -DPYTHON_EXECUTABLE=/opt/python/cp312-cp312/bin/python3.12 && \ - /usr/bin/cmake --build --preset linux_gcc_cpu_release" - displayName: 'Build Python Wheel 3.12' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - task: CopyFiles@2 - displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)/onnxruntime-genai/build/gcc_cpu/release/wheel' - Contents: '*manylinux*.whl' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime python wheel' - inputs: - ArtifactName: onnxruntime-genai-linux-cpu-$(buildArch) - - - template: steps/component-governance-component-detection-step.yml - parameters: - condition: 'succeeded' - - - task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3 - displayName: 'Clean Agent Directories' - condition: always() \ No newline at end of file diff --git a/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml b/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml deleted file mode 100644 index af009c1c2..000000000 --- a/.pipelines/stages/jobs/py-linux-gpu-packaging-job.yml +++ /dev/null @@ -1,163 +0,0 @@ -parameters: -- name: cuda_version - type: string - default: '11.8' - values: - - 11.8 - - 12.2 -- name: ort_version - type: string - default: '1.17.1' -jobs: -- job: Linux_GPU_Wheels - timeoutInMinutes: 240 - workspace: - clean: all - pool: onnxruntime-Ubuntu2204-AMD-CPU - variables: - # The build machine pool doesn't have dotnet, so it can't run CG. - - name: skipComponentGovernanceDetection - value: true - - name: ort_version - value: '1.17.1' - - steps: - - checkout: self # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/onnxruntime-genai, - clean: true - submodules: recursive - - - checkout: manylinux # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/manylinux, - submodules: false - - - template: steps/set-nightly-build-option-variable-step.yml - - - task: DownloadGitHubRelease@0 - inputs: - connection: 'GitHub - Release' - userRepository: 'microsoft/onnxruntime' - defaultVersionType: 'specificTag' - version: 'v${{ parameters.ort_version }}' - itemPattern: 'onnxruntime-linux-x64-gpu-${{ parameters.ort_version }}*' - downloadPath: '$(Build.SourcesDirectory)' - displayName: Download the ONNXRuntime prebuilt package. - - - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '**/*.tgz' - destinationFolder: '$(Build.SourcesDirectory)/onnxruntime-genai' - cleanDestinationFolder: false - overwriteExistingFiles: true - displayName: Unpack ONNXRuntime package. - - - bash: | - mv onnxruntime-linux-x64-gpu-${{ parameters.ort_version }} ort - rm -rf ort/lib/*tensorrt* - displayName: Rename Onnxruntime to ort - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - script: | - set -e -x - az login --identity --username 63b63039-6328-442f-954b-5a64d124e5b4 - az acr login --name onnxruntimebuildcache --subscription 00c06639-6ee4-454e-8058-8d8b1703bd87 - python3 tools/ci_build/get_docker_image.py --dockerfile tools/ci_build/github/linux/docker/manylinux//Dockerfile.manylinux2_28_cuda \ - --context tools/ci_build/github/linux/docker/manylinux \ - --docker-build-args "--build-arg BUILD_UID=$( id -u )" \ - --container-registry onnxruntimebuildcache \ - --manylinux-src $(Build.SourcesDirectory)/manylinux \ - --multiple_repos \ - --repository onnxruntimegpubuild - displayName: 'Get Docker Image' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimegpubuild \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cuda_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON \ - -DCMAKE_CUDA_ARCHITECTURES=\"60;61;70;75;80;86\" \ - -DPYTHON_EXECUTABLE=/opt/python/cp38-cp38/bin/python3.8 && \ - /usr/bin/cmake --build --preset linux_gcc_cuda_release" - displayName: 'Build Python Wheel 3.8' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimegpubuild \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cuda_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON -DCMAKE_CUDA_ARCHITECTURES=\"60;61;70;75;80;86\" \ - -DPYTHON_EXECUTABLE=/opt/python/cp39-cp39/bin/python3.9 && \ - /usr/bin/cmake --build --preset linux_gcc_cuda_release" - displayName: 'Build Python Wheel 3.9' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimegpubuild \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cuda_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON -DCMAKE_CUDA_ARCHITECTURES=\"60;61;70;75;80;86\" \ - -DPYTHON_EXECUTABLE=/opt/python/cp310-cp310/bin/python3.10 && \ - /usr/bin/cmake --build --preset linux_gcc_cuda_release" - displayName: 'Build Python Wheel 3.10' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimegpubuild \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cuda_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON -DCMAKE_CUDA_ARCHITECTURES=\"60;61;70;75;80;86\" \ - -DPYTHON_EXECUTABLE=/opt/python/cp311-cp311/bin/python3.11 && \ - /usr/bin/cmake --build --preset linux_gcc_cuda_release" - displayName: 'Build Python Wheel 3.11' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - script: | - set -e -x - docker run \ - --rm \ - --volume $(Build.SourcesDirectory)/onnxruntime-genai:/onnxruntime_src \ - -w /onnxruntime_src/ onnxruntimegpubuild \ - bash -c " \ - /usr/bin/cmake --preset linux_gcc_cuda_release \ - -DENABLE_TESTS=OFF \ - -DMANYLINUX=ON -DCMAKE_CUDA_ARCHITECTURES=\"60;61;70;75;80;86\" \ - -DPYTHON_EXECUTABLE=/opt/python/cp312-cp312/bin/python3.12 && \ - /usr/bin/cmake --build --preset linux_gcc_cuda_release" - displayName: 'Build Python Wheel 3.12' - workingDirectory: '$(Build.SourcesDirectory)/onnxruntime-genai' - - - task: CopyFiles@2 - displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)/onnxruntime-genai/build/gcc_cuda/release/wheel' - Contents: '*manylinux*.whl' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime python wheel' - inputs: - ArtifactName: onnxruntime-genai-linux-gpu - - - template: steps/component-governance-component-detection-step.yml - parameters: - condition: 'succeeded' - - - task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3 - displayName: 'Clean Agent Directories' - condition: always() diff --git a/.pipelines/stages/jobs/py-linux-packaging-job.yml b/.pipelines/stages/jobs/py-linux-packaging-job.yml new file mode 100644 index 000000000..5b607b7cf --- /dev/null +++ b/.pipelines/stages/jobs/py-linux-packaging-job.yml @@ -0,0 +1,67 @@ +parameters: +- name: arch + type: string +- name: ort_version + type: string +- name: ep + type: string +- name: cuda_version + type: string + default: '' +jobs: +- job: Linux_${{ parameters.ep }}_${{ parameters.arch }}_Wheels + strategy: + matrix: + Python38: + PyDotVer: '3.8' + PyNoDotVer: '38' + Python39: + PyDotVer: '3.9' + PyNoDotVer: '39' + Python310: + PyDotVer: '3.10' + PyNoDotVer: '310' + Python311: + PyDotVer: '3.11' + PyNoDotVer: '311' + Python312: + PyDotVer: '3.12' + PyNoDotVer: '312' + timeoutInMinutes: 240 + workspace: + clean: all + pool: 'onnxruntime-Ubuntu2204-AMD-CPU' + variables: + # The build machine pool doesn't have dotnet, so it can't run CG. + - name: skipComponentGovernanceDetection + value: true + - name: arch + value: ${{ parameters.arch }} + - name: ep + value: ${{ parameters.ep }} + - name: cuda_version + value: ${{ parameters.cuda_version }} + - name: ort_version + value: ${{ parameters.ort_version }} + - name: ort_filename + ${{ if eq(parameters.ep, 'cpu') }}: + value: 'onnxruntime-linux-${{ parameters.arch }}-${{ parameters.ort_version }}' + ${{ else}}: + ${{if eq(parameters.cuda_version, '11.8') }}: + value: 'onnxruntime-linux-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' + ${{ else }}: + value: 'onnxruntime-linux-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' + steps: + + - template: steps/capi-linux-step.yml + parameters: + target: 'python' + genai_src: '$(Build.SourcesDirectory)/onnxruntime-genai' + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime python wheel' + inputs: + ArtifactName: onnxruntime-genai-linux-$(ep)-$(arch) + + - template: steps/compliant-and-cleanup-step.yml + diff --git a/.pipelines/stages/jobs/py-win-cpu-packaging-job.yml b/.pipelines/stages/jobs/py-win-cpu-packaging-job.yml deleted file mode 100644 index 172371acf..000000000 --- a/.pipelines/stages/jobs/py-win-cpu-packaging-job.yml +++ /dev/null @@ -1,146 +0,0 @@ -parameters: - arch: 'x64' - ort_version: '1.17.1' - -jobs: -- job: Windows_CPU_Wheels - pool: 'onnxruntime-Win-CPU-2022' - strategy: - matrix: - Python38_x64: - PythonVersion: '3.8' - buildArch: ${{ parameters.arch }} - Python39_x64: - PythonVersion: '3.9' - buildArch: ${{ parameters.arch }} - Python310_x64: - PythonVersion: '3.10' - buildArch: ${{ parameters.arch }} - Python311_x64: - PythonVersion: '3.11' - buildArch: ${{ parameters.arch }} - Python312_x64: - PythonVersion: '3.12' - buildArch: ${{ parameters.arch }} - variables: - BuildConfig: 'RelWithDebInfo' - ort_version: '1.17.1' - timeoutInMinutes: 180 - workspace: - clean: all - steps: - - checkout: self - clean: true - submodules: recursive - - task: UsePythonVersion@0 - inputs: - versionSpec: $(PythonVersion) - addToPath: true - architecture: $(buildArch) - - task: onebranch.pipeline.tsaoptions@1 - displayName: 'OneBranch TSAOptions' - inputs: - tsaConfigFilePath: '$(Build.SourcesDirectory)\.config\tsaoptions.json' - appendSourceBranchName: false - - - task: PythonScript@0 - inputs: - scriptSource: inline - script: | - import sys - import subprocess - subprocess.call(['pip', 'install', '-q', 'setuptools', 'wheel', 'build', 'packaging']) - workingDirectory: '$(Build.BinariesDirectory)' - displayName: 'Install python modules' - - - template: steps/set-nightly-build-option-variable-step.yml - - - task: DownloadGitHubRelease@0 - inputs: - connection: 'GitHub - Release' - userRepository: 'microsoft/onnxruntime' - defaultVersionType: 'specificTag' - version: 'v${{ parameters.ort_version }}' - itemPattern: 'onnxruntime-win-$(buildArch)-${{ parameters.ort_version }}*' - downloadPath: '$(Build.SourcesDirectory)' - displayName: Download ONNXRuntime - - - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '**/*.zip' - destinationFolder: '$(Build.SourcesDirectory)' - cleanDestinationFolder: false - overwriteExistingFiles: true - displayName: Unzip OnnxRuntime - - - powershell: | - Rename-Item -Path onnxruntime-win-$(buildArch)-${{ parameters.ort_version }} -NewName ort - displayName: Rename Onnxruntime to ort - workingDirectory: '$(Build.SourcesDirectory)' - - - bash: | - cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=$(BuildConfig) -DUSE_CUDA=OFF -DENABLE_TESTS=OFF -S . -B build - cmake --build build --config $(BuildConfig) --parallel --target python - displayName: 'Build pybind with CMake' - - # BinSkim is skipped because onnxruntime-genai is build as static library - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory)\build\wheel -Recurse - - - template: steps/win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)\build\wheel\onnxruntime_genai' - DisplayName: 'ESRP - Sign Native dlls' - DoEsrp: true - Pattern: '*.pyd,*.dll' - # - - bash: | - cmake --build build --config $(BuildConfig) --parallel --target PyPackageBuild - displayName: 'Build Python Wheel' - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse - - - task: CopyFiles@2 - displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\build\wheel\' - Contents: '*.whl' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai python wheel' - inputs: - ArtifactName: onnxruntime-genai-win-cpu-$(buildArch) - - - script: | - 7z x *.whl - workingDirectory: '$(Build.ArtifactStagingDirectory)' - displayName: 'unzip the package' - - - powershell: | - Get-ChildItem -Path $(Build.ArtifactStagingDirectory) -Recurse - displayName: 'Verify the package' - - - task: CredScan@3 - displayName: 'Run CredScan' - inputs: - debugMode: false - continueOnError: true - - - task: TSAUpload@2 - displayName: 'TSA upload' - condition: and(and (succeeded(), and(eq(variables['buildArch'], 'x64'), eq(variables['PythonVersion'], '3.8'))), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - GdnPublishTsaOnboard: false - GdnPublishTsaConfigFile: '$(Build.sourcesDirectory)\.config\tsaoptions.json' - continueOnError: true - - - template: steps/component-governance-component-detection-step.yml - parameters: - condition: 'succeeded' - - - task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3 - displayName: 'Clean Agent Directories' - condition: always() \ No newline at end of file diff --git a/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml b/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml deleted file mode 100644 index 2e124c9e7..000000000 --- a/.pipelines/stages/jobs/py-win-gpu-packaging-job.yml +++ /dev/null @@ -1,154 +0,0 @@ -parameters: -- name: ort_version - type: string - default: '1.17.1' -jobs: -- job: Windows_GPU_Wheels - pool: 'onnxruntime-Win-CPU-2022' - # pool: 'onnxruntime-Win2022-GPU-A10' - strategy: - matrix: - Python38: - PythonVersion: '3.8' - Python39: - PythonVersion: '3.9' - Python310: - PythonVersion: '3.10' - Python311: - PythonVersion: '3.11' - Python312: - PythonVersion: '3.12' - variables: - BuildConfig: 'RelWithDebInfo' - cuda_version: '11.8' - cuda_dir: '$(Build.SourcesDirectory)\cuda_sdk' - timeoutInMinutes: 180 - workspace: - clean: all - steps: - - checkout: self - clean: true - submodules: recursive - - task: UsePythonVersion@0 - inputs: - versionSpec: $(PythonVersion) - addToPath: true - architecture: x64 - - task: onebranch.pipeline.tsaoptions@1 - displayName: 'OneBranch TSAOptions' - inputs: - tsaConfigFilePath: '$(Build.SourcesDirectory)\.config\tsaoptions.json' - appendSourceBranchName: false - - - task: PythonScript@0 - inputs: - scriptSource: inline - script: | - import sys - import subprocess - subprocess.call(['pip', 'install', '-q', 'setuptools', 'wheel', 'build', 'packaging']) - workingDirectory: '$(Build.BinariesDirectory)' - displayName: 'Install python modules' - - - template: steps/set-nightly-build-option-variable-step.yml - - - task: DownloadGitHubRelease@0 - inputs: - connection: 'GitHub - Release' - userRepository: 'microsoft/onnxruntime' - defaultVersionType: 'specificTag' - version: 'v${{ parameters.ort_version }}' - itemPattern: 'onnxruntime-win-x64-gpu-${{ parameters.ort_version }}.zip' - downloadPath: '$(Build.SourcesDirectory)' - displayName: Download ONNXRuntime - - - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '**/*.zip' - destinationFolder: '$(Build.SourcesDirectory)' - cleanDestinationFolder: false - overwriteExistingFiles: true - displayName: Unzip OnnxRuntime - - - powershell: | - Rename-Item -Path onnxruntime-win-x64-gpu-${{ parameters.ort_version }} -NewName ort - Remove-Item -Path ort\lib\**tensorrt* -ErrorAction SilentlyContinue - displayName: Rename Onnxruntime to ort - workingDirectory: '$(Build.SourcesDirectory)' - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse - - - powershell: | - azcopy.exe cp --recursive "https://lotusscus.blob.core.windows.net/models/cuda_sdk/v$(cuda_version)" $(cuda_dir) - displayName: 'Download CUDA' - - - powershell: | - Get-ChildItem -Path $(cuda_dir) -Recurse - displayName: 'Verify CUDA download' - - - powershell: | - cmake -G "Visual Studio 17 2022" -A x64 -T cuda=$(cuda_dir)\v$(cuda_version) -DCMAKE_BUILD_TYPE=$(BuildConfig) -DENABLE_TESTS=OFF -DUSE_CUDA=TRUE -S . -B build - cmake --build build --config $(BuildConfig) --parallel --target python - displayName: 'Build pybind with CMake' - -# BinSkim is skipped because onnxruntime-genai is build as static library - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory)\build\wheel\ -Recurse - # Esrp signing - - template: steps/win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)\build\wheel\onnxruntime_genai\' - DisplayName: 'ESRP - Sign Native dlls' - DoEsrp: true - Pattern: '*.pyd,*.dll' - - - bash: | - cmake --build build --config $(BuildConfig) --parallel --target PyPackageBuild - displayName: 'Build Python Wheel' - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse - - task: CopyFiles@2 - displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\build\wheel\' - Contents: '*.whl' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai python wheel' - inputs: - ArtifactName: onnxruntime-genai-win-gpu - - - script: | - 7z x *.whl - workingDirectory: '$(Build.ArtifactStagingDirectory)' - displayName: 'unzip the package' - - - task: CredScan@3 - displayName: 'Run CredScan' - inputs: - debugMode: false - continueOnError: true - - - powershell: | - Get-ChildItem -Path $(Build.ArtifactStagingDirectory) -Recurse - displayName: 'Verify the package' - - - task: TSAUpload@2 - displayName: 'TSA upload' - condition: and(and (succeeded(), eq(variables['PythonVersion'], '3.8')), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - inputs: - GdnPublishTsaOnboard: false - GdnPublishTsaConfigFile: '$(Build.sourcesDirectory)\.config\tsaoptions.json' - continueOnError: true - - - template: steps/component-governance-component-detection-step.yml - parameters: - condition: 'succeeded' - - - task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3 - displayName: 'Clean Agent Directories' - condition: always() diff --git a/.pipelines/stages/jobs/py-win-packaging-job.yml b/.pipelines/stages/jobs/py-win-packaging-job.yml new file mode 100644 index 000000000..4d85511be --- /dev/null +++ b/.pipelines/stages/jobs/py-win-packaging-job.yml @@ -0,0 +1,95 @@ +parameters: +- name: arch + type: string +- name: ort_version + type: string +- name: cuda_version + type: string + default: '' +- name: ep + type: string +jobs: +- job: Windows_${{ parameters.ep }}_${{ parameters.arch }}_Wheels + pool: 'onnxruntime-Win-CPU-2022' + strategy: + matrix: + Python38_x64: + PythonVersion: '3.8' + Python39_x64: + PythonVersion: '3.9' + Python310_x64: + PythonVersion: '3.10' + Python311_x64: + PythonVersion: '3.11' + Python312_x64: + PythonVersion: '3.12' + timeoutInMinutes: 180 + variables: + - name: ep + value: ${{ parameters.ep }} + - name: cuda_version + value: ${{ parameters.cuda_version }} + - name: arch + value: ${{ parameters.arch }} + - name: ort_version + value: ${{ parameters.ort_version }} + - name: ort_filename + ${{ if eq(parameters.ep, 'cpu') }}: + value: 'onnxruntime-win-${{ parameters.arch }}-${{ parameters.ort_version }}' + ${{ else}}: + ${{if eq(parameters.cuda_version, '11.8') }}: + value: 'onnxruntime-win-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' + ${{ else }}: + value: 'onnxruntime-win-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' + workspace: + clean: all + steps: + + - task: UsePythonVersion@0 + inputs: + versionSpec: $(PythonVersion) + addToPath: true + architecture: $(arch) + + - task: PythonScript@0 + inputs: + scriptSource: inline + script: | + import sys + import subprocess + subprocess.call(['pip', 'install', '-q', 'setuptools', 'wheel', 'build', 'packaging']) + workingDirectory: '$(Build.BinariesDirectory)' + displayName: 'Install python modules' + + - template: steps/capi-win-step.yml + parameters: + target: 'python' +# ep: ${{ parameters.ep }} + + - template: steps/compliant/win-esrp-dll-step.yml + parameters: + FolderPath: '$(Build.SourcesDirectory)\build\release\$(ep)_default\wheel\onnxruntime_genai' + DisplayName: 'ESRP - PYD Sign' + DoEsrp: true + Pattern: '*.pyd' + + - powershell: | + cmake --build --preset windows_$(arch)_$(ep)_release --parallel --PyPackageBuild + displayName: 'Build Python Wheel' + + - powershell: | + Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse + + - task: CopyFiles@2 + displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\build\release\$(ep)_default\wheel' + Contents: '*.whl' + TargetFolder: '$(Build.ArtifactStagingDirectory)' + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime Genai python wheel' + inputs: + ArtifactName: onnxruntime-genai-win-$(ep)-$(arch) + + - template: steps/compliant-and-cleanup-step.yml \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/capi-linux-step.yml b/.pipelines/stages/jobs/steps/capi-linux-step.yml new file mode 100644 index 000000000..2007ef670 --- /dev/null +++ b/.pipelines/stages/jobs/steps/capi-linux-step.yml @@ -0,0 +1,106 @@ +parameters: +- name: genai_src + type: string +- name: target + type: string +steps: + +- checkout: self # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/onnxruntime-genai, + clean: true + submodules: recursive + +- checkout: manylinux # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/manylinux, + clean: true + submodules: recursive + +- template: utils/set-nightly-build-option-variable.yml + +- bash: | + echo "arch=$(arch)" + echo "ep=$(ep)" + echo "genai_src=${{ parameters.genai_src }}" + displayName: 'Print Parameters' + +- template: utils/download-ort.yml + parameters: + archiveType: 'tgz' + genai_src: ${{ parameters.genai_src }} +- bash: | + set -e -x + az login --identity --username 63b63039-6328-442f-954b-5a64d124e5b4 + az acr login --name onnxruntimebuildcache --subscription 00c06639-6ee4-454e-8058-8d8b1703bd87 + python3 tools/ci_build/get_docker_image.py --dockerfile tools/ci_build/github/linux/docker/manylinux/Dockerfile.manylinux2_28_$(ep) \ + --context tools/ci_build/github/linux/docker/manylinux \ + --docker-build-args "--build-arg BUILD_UID=$( id -u )" \ + --container-registry onnxruntimebuildcache \ + --manylinux-src $(Build.SourcesDirectory)/manylinux \ + --multiple_repos \ + --repository onnxruntime$(ep)build$(arch) + displayName: 'Get Docker Image' + workingDirectory: '${{ parameters.genai_src }}' + +- ${{ if eq(parameters.target, 'onnxruntime-genai') }}: + - script: | + set -e -x + docker run \ + --rm \ + --volume ${{ parameters.genai_src }}:/ort_genai_src \ + -w /ort_genai_src/ onnxruntime$(ep)build$(arch) \ + bash -c " \ + /usr/bin/cmake --preset linux_gcc_$(ep)_release \ + -DENABLE_TESTS=OFF && \ + /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ + --target onnxruntime-genai" + displayName: 'Build GenAi' + workingDirectory: '${{ parameters.genai_src }}' + - task: BinSkim@4 + displayName: 'Run BinSkim' + inputs: + AnalyzeTargetGlob: '$(Build.SourcesDirectory)/onnxruntime-genai/build/**/*genai.so' + continueOnError: true + - template: utils/capi-archive.yml + parameters: + genai_src: ${{ parameters.genai_src }} + archiveType: tar +- ${{ if eq(parameters.target, 'python') }}: + - bash: | + set -e -x + docker run \ + --rm \ + --volume ${{ parameters.genai_src }}:/ort_genai_src \ + -w /ort_genai_src/ onnxruntime$(ep)build$(arch) \ + bash -c " \ + /usr/bin/cmake --preset linux_gcc_$(ep)_release \ + -DENABLE_TESTS=OFF \ + -DMANYLINUX=ON \ + -DPYTHON_EXECUTABLE=/opt/python/cp$(PyNoDotVer)-cp$(PyNoDotVer)/bin/python$(PyDotVer) && \ + /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ + --target python" + displayName: 'Build Python $(PyNoDotVer)' + workingDirectory: '${{ parameters.genai_src }}' + - bash: | + set -e -x + docker run \ + --rm \ + --volume ${{ parameters.genai_src }}:/ort_genai_src \ + -w /ort_genai_src/ onnxruntime$(ep)build$(arch) \ + bash -c " \ + /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ + -DENABLE_TESTS=OFF \ + -DMANYLINUX=ON \ + -DPYTHON_EXECUTABLE=/opt/python/cp$(PyNoDotVer)-cp$(PyNoDotVer)/bin/python$(PyDotVer) && \ + /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ + --target PyPackageBuild" + displayName: 'PyPackageBuild $(PyNoDotVer)' + workingDirectory: '${{ parameters.genai_src }}' + - task: CopyFiles@2 + displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' + inputs: + SourceFolder: '${{ parameters.genai_src }}/build/gcc_$(ep)/release/wheel' + Contents: '*manylinux*.whl' + TargetFolder: '$(Build.ArtifactStagingDirectory)' + +- script: | + ls ${{ parameters.genai_src }} -R + displayName: 'List files from SourceDirectory' + diff --git a/.pipelines/stages/jobs/steps/capi-win-build-step.yml b/.pipelines/stages/jobs/steps/capi-win-build-step.yml deleted file mode 100644 index 38b3a99f6..000000000 --- a/.pipelines/stages/jobs/steps/capi-win-build-step.yml +++ /dev/null @@ -1,98 +0,0 @@ -parameters: - arch: 'x64' - ort_version: '1.17.1' - ep: 'cpu' - cuda_version: '11.8' - -steps: -- checkout: self - clean: true - submodules: recursive -- task: onebranch.pipeline.tsaoptions@1 - displayName: 'OneBranch TSAOptions' - inputs: - tsaConfigFilePath: '$(Build.SourcesDirectory)\.config\tsaoptions.json' - appendSourceBranchName: false - -- template: set-nightly-build-option-variable-step.yml - -- task: DownloadGitHubRelease@0 - inputs: - connection: 'GitHub - Release' - userRepository: 'microsoft/onnxruntime' - defaultVersionType: 'specificTag' - version: 'v${{ parameters.ort_version }}' - ${{ if eq(parameters.ep, 'gpu') }}: - ${{ if eq(parameters.cuda_version, '11.8') }}: - itemPattern: 'onnxruntime-win-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}*' - ${{ else }}: - itemPattern: 'onnxruntime-win-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}*' - ${{ else }}: - itemPattern: 'onnxruntime-win-${{ parameters.arch }}-${{ parameters.ort_version }}*' - downloadPath: '$(Build.SourcesDirectory)' - displayName: Download ONNXRuntime ${{ parameters.arch }} ${{ parameters.ep }} - -- task: ExtractFiles@1 - inputs: - archiveFilePatterns: '**/*.zip' - destinationFolder: '$(Build.SourcesDirectory)' - cleanDestinationFolder: false - overwriteExistingFiles: true - displayName: Unzip OnnxRuntime - -- powershell: | - Rename-Item -Path onnxruntime-win-${{ parameters.arch }}-${{ parameters.ort_version }} -NewName ort - displayName: Rename Onnxruntime CPU to ort - condition: eq('${{ parameters.ep }}', 'cpu') - workingDirectory: '$(Build.SourcesDirectory)' - -- powershell: | - Rename-Item -Path onnxruntime-win-${{ parameters.arch }}-gpu-${{ parameters.ort_version }} -NewName ort - displayName: Rename Onnxruntime CUDA 11 to ort - condition: and(eq('${{ parameters.ep }}', 'gpu'), eq('${{ parameters.cuda_version }}', '11.8')) - workingDirectory: '$(Build.SourcesDirectory)' - -- powershell: | - Rename-Item -Path onnxruntime-win-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }} -NewName ort - displayName: Rename Onnxruntime CUDA 12 to ort - condition: and(eq('${{ parameters.ep }}', 'gpu'), eq('${{ parameters.cuda_version }}', '12.2')) - workingDirectory: '$(Build.SourcesDirectory)' - -- bash: | - set -e -x - azcopy.exe cp --recursive "https://lotusscus.blob.core.windows.net/models/cuda_sdk/v${{ parameters.cuda_version }}" '$(Build.SourcesDirectory)\cuda_sdk' - displayName: 'Download CUDA' - condition: eq('${{ parameters.ep }}', 'gpu') - workingDirectory: '$(Build.SourcesDirectory)' -- bash: | - set -e -x - cmake --preset windows_x64_cuda_release -T cuda='$(Build.SourcesDirectory)\cuda_sdk\v${{ parameters.cuda_version }}' - cmake --build --preset windows_x64_cuda_release --parallel - displayName: 'Configure CMake C API with CUDA' - condition: eq('${{ parameters.ep }}', 'gpu') - workingDirectory: '$(Build.SourcesDirectory)' - -- bash: | - set -e -x - cmake --preset windows_x64_cpu_release - cmake --build --preset windows_x64_cpu_release --parallel - displayName: 'Configure CMake C API without CUDA' - condition: eq('${{ parameters.ep }}', 'cpu') - workingDirectory: '$(Build.SourcesDirectory)' - -- ${{ if eq(parameters.ep, 'cpu') }}: - - template: win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)\build\release\cpu_default\Release' - DisplayName: 'ESRP - Sign Native dlls' -- ${{ else }}: - - template: win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)\build\release\cuda_default\Release' - DisplayName: 'ESRP - Sign C++ dlls' - -- task: BinSkim@4 - displayName: 'Run BinSkim' - inputs: - AnalyzeTargetGlob: '$(Build.SourcesDirectory)\**\*genai.dll' - continueOnError: true \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/capi-win-step.yml b/.pipelines/stages/jobs/steps/capi-win-step.yml new file mode 100644 index 000000000..ac6baedbe --- /dev/null +++ b/.pipelines/stages/jobs/steps/capi-win-step.yml @@ -0,0 +1,82 @@ +parameters: +- name: target + type: string + default: 'onnxruntime-genai' +steps: +- bash: | + echo "##[error]Error: ep and arch are not set" + exit 1 + displayName: 'Check if variables arch and ep are set' + condition: or( eq (variables['ep'], ''), eq (variables['arch'], '')) + +- checkout: self + clean: true + submodules: recursive +- task: onebranch.pipeline.tsaoptions@1 + displayName: 'OneBranch TSAOptions' + inputs: + tsaConfigFilePath: '$(Build.SourcesDirectory)\.config\tsaoptions.json' + appendSourceBranchName: false + +- template: utils/set-nightly-build-option-variable.yml + +- script: | + echo "arch=$(arch)" + echo "ort_version=$(ort_version)" + echo "ep=$(ep)" + echo "cuda_version=$(cuda_version)" + echo "target=${{ parameters.target }}" + echo "ort_filename=$(ort_filename)" + displayName: 'Print Parameters' + +- template: utils/download-ort.yml + parameters: + archiveType: 'zip' + genai_src: '$(Build.SourcesDirectory)' + +- powershell: | + azcopy.exe cp --recursive "https://lotusscus.blob.core.windows.net/models/cuda_sdk/v$(cuda_version)" '$(Build.SourcesDirectory)\cuda_sdk' + displayName: 'Download CUDA' + condition: eq(variables['ep'], 'cuda') + workingDirectory: '$(Build.SourcesDirectory)' + +- powershell: | + cmake --preset windows_$(arch)_$(ep)_release -T cuda='$(Build.SourcesDirectory)\cuda_sdk\v$(cuda_version)' + displayName: 'Configure CMake C API with CUDA' + condition: eq(variables['ep'], 'cuda') + workingDirectory: '$(Build.SourcesDirectory)' + +- powershell: | + cmake --preset windows_$(arch)_$(ep)_release + displayName: 'Configure CMake C API without CUDA' + condition: ne(variables['ep'], 'cuda') + workingDirectory: '$(Build.SourcesDirectory)' + +- powershell: | + cmake --build --preset windows_$(arch)_$(ep)_release --parallel --target ${{ parameters.target }} + displayName: 'Build C API' + workingDirectory: '$(Build.SourcesDirectory)' + +- ${{ if eq(parameters.target, 'onnxruntime-genai') }}: + - template: compliant/win-esrp-dll-step.yml + parameters: + FolderPath: '$(buildDir)' + DisplayName: 'ESRP - Sign C++ dlls' + + - task: BinSkim@4 + displayName: 'Run BinSkim' + inputs: + AnalyzeTargetGlob: '$(Build.SourcesDirectory)\**\*genai.dll' + continueOnError: true + + - template: utils/capi-archive.yml + parameters: + genai_src: '$(Build.SourcesDirectory)' + archiveType: zip + +- ${{ if eq(parameters.target, 'python') }}: + - task: BinSkim@4 + displayName: 'Run BinSkim' + inputs: + AnalyzeTargetGlob: '$(Build.SourcesDirectory)\**\*.pyd' + continueOnError: true diff --git a/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml b/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml index 625c82aea..fb8a420af 100644 --- a/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml +++ b/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml @@ -13,7 +13,7 @@ steps: GdnPublishTsaConfigFile: '$(Build.sourcesDirectory)\.config\tsaoptions.json' continueOnError: true -- template: component-governance-component-detection-step.yml +- template: compliant/component-governance-component-detection-step.yml parameters: condition: 'succeeded' diff --git a/.pipelines/stages/jobs/steps/component-governance-component-detection-step.yml b/.pipelines/stages/jobs/steps/compliant/component-governance-component-detection-step.yml similarity index 100% rename from .pipelines/stages/jobs/steps/component-governance-component-detection-step.yml rename to .pipelines/stages/jobs/steps/compliant/component-governance-component-detection-step.yml diff --git a/.pipelines/stages/jobs/steps/win-esrp-dll-step.yml b/.pipelines/stages/jobs/steps/compliant/win-esrp-dll-step.yml similarity index 100% rename from .pipelines/stages/jobs/steps/win-esrp-dll-step.yml rename to .pipelines/stages/jobs/steps/compliant/win-esrp-dll-step.yml diff --git a/.pipelines/stages/jobs/steps/nuget-win-step.yml b/.pipelines/stages/jobs/steps/nuget-win-step.yml new file mode 100644 index 000000000..47cb4c338 --- /dev/null +++ b/.pipelines/stages/jobs/steps/nuget-win-step.yml @@ -0,0 +1,44 @@ +steps: +- powershell: | + dotnet build Microsoft.ML.OnnxRuntimeGenAI.csproj -p:Configuration="$(buildConfig)" -p:NativeBuildOutputDir="$(buildDir)\$(buildConfig)" + displayName: 'Build CSharp' + workingDirectory: '$(Build.SourcesDirectory)\src\csharp' + +- task: BinSkim@4 + displayName: 'Run BinSkim' + inputs: + AnalyzeTargetGlob: '$(Build.SourcesDirectory)\src\csharp\**\*.dll' + continueOnError: true + +- template: compliant/win-esrp-dll-step.yml + parameters: + FolderPath: '$(Build.SourcesDirectory)\src\csharp\bin\Release\' + DisplayName: 'ESRP - Sign C# dlls' + +- powershell: | + $VERSION = '0.1.0-rc1' + nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` + -Prop version=$VERSION ` + -Prop genai_nuget_ext=$(genai_nuget_ext) ` + -Prop ort_nuget_ext=$(ort_nuget_ext) ` + -Prop ort_version=$(ort_version) ` + -Prop configuration=$(buildConfig) ` + -Prop buildPath=$(buildDir) + nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.Managed.nuspec ` + -Prop version=$VERSION ` + -Prop configuration=$(buildConfig) + displayName: 'Nuget Packaging' + workingDirectory: '$(Build.SourcesDirectory)\nuget' + +- powershell: | + Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse + displayName: 'List all files in the repo for' + +- powershell: | + Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse +- task: CopyFiles@2 + displayName: 'Copy Nuget to: $(Build.ArtifactStagingDirectory)' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\nuget' + Contents: '*.nupkg' + TargetFolder: '$(Build.ArtifactStagingDirectory)' \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/utils/capi-archive.yml b/.pipelines/stages/jobs/steps/utils/capi-archive.yml new file mode 100644 index 000000000..3c872a85d --- /dev/null +++ b/.pipelines/stages/jobs/steps/utils/capi-archive.yml @@ -0,0 +1,74 @@ +parameters: +- name: genai_src + type: string +- name: archiveType + type: string +steps: +- bash: | + echo "##[error]Error: artifactName and buildDir are not set" + exit 1 + displayName: 'Check if variables ort_filename and ort_filename are set' + condition: or( eq (variables['artifactName'], ''), eq (variables['buildDir'], '')) + +- task: CopyFiles@2 + displayName: 'Copy Onnxruntime C API library files to ArtifactStagingDirectory' + inputs: + SourceFolder: '${{ parameters.genai_src }}/ort/lib' + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(artifactName)\lib' + +- ${{ if eq(parameters.archiveType, 'tar') }}: + - task: CopyFiles@2 + displayName: 'Copy Onnxruntime C API dll files to ArtifactStagingDirectory' + inputs: + SourceFolder: '${{ parameters.genai_src }}/$(buildDir)' + Contents: | + onnxruntime-genai.so + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(artifactName)\lib' +- ${{ else }}: + - task: CopyFiles@2 + displayName: 'Copy Onnxruntime C API dll files to ArtifactStagingDirectory' + inputs: + SourceFolder: '${{ parameters.genai_src }}\$(buildDir)\Release' + Contents: | + onnxruntime-genai.dll + onnxruntime-genai.lib + onnxruntime-genai.pdb + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(artifactName)\lib' + +- task: CopyFiles@2 + displayName: 'Copy GenAi C API header to ArtifactStagingDirectory' + inputs: + SourceFolder: '${{ parameters.genai_src }}/src' + Contents: | + ort_genai_c.h + TargetFolder: '$(Build.ArtifactStagingDirectory)/$(artifactName)/include' + +- task: CopyFiles@2 + displayName: 'Copy other files to ArtifactStagingDirectory' + inputs: + SourceFolder: '${{ parameters.genai_src }}' + Contents: | + VERSION_INFO + LICENSE + SECURITY.md + README.md + ThirdPartyNotices.txt + TargetFolder: '$(Build.ArtifactStagingDirectory)/$(artifactName)' + +- task: ArchiveFiles@2 + inputs: + rootFolderOrFile: '$(Build.ArtifactStagingDirectory)/$(artifactName)' + includeRootFolder: true + archiveType: ${{ parameters.archiveType }} + ${{ if eq(parameters.archiveType, 'tar') }}: + tarCompression: 'gz' + archiveFile: '$(Build.ArtifactStagingDirectory)/$(artifactName).tgz' + ${{ else }}: + archiveFile: '$(Build.ArtifactStagingDirectory)/$(artifactName).zip' + replaceExistingArchive: true + +- task: DeleteFiles@1 + inputs: + SourceFolder: '$(Build.ArtifactStagingDirectory)/$(artifactName)' + Contents: '*' + RemoveSourceFolder: true diff --git a/.pipelines/stages/jobs/steps/utils/download-ort.yml b/.pipelines/stages/jobs/steps/utils/download-ort.yml new file mode 100644 index 000000000..f9b5001d8 --- /dev/null +++ b/.pipelines/stages/jobs/steps/utils/download-ort.yml @@ -0,0 +1,43 @@ +parameters: +- name: genai_src + type: string +- name: archiveType + type: string +steps: +- bash: | + echo "##[error]Error: ort_version and ort_filename are not set" + exit 1 + displayName: 'Check if variables ort_filename and ort_filename are set' + condition: or( eq (variables['ort_version'], ''), eq (variables['ort_filename'], '')) + +- task: DownloadGitHubRelease@0 + inputs: + connection: 'GitHub - Release' + userRepository: 'microsoft/onnxruntime' + defaultVersionType: 'specificTag' + version: 'v$(ort_version)' + itemPattern: '$(ort_filename)*' + downloadPath: '${{ parameters.genai_src }}' + displayName: Download $(ort_filename) + +- task: ExtractFiles@1 + inputs: + archiveFilePatterns: '**/*.${{ parameters.archiveType }}' + destinationFolder: '${{ parameters.genai_src }}' + cleanDestinationFolder: false + overwriteExistingFiles: true + displayName: Unzip OnnxRuntime + +- task: CopyFiles@2 + inputs: + SourceFolder: '${{ parameters.genai_src }}/$(ort_filename)' + TargetFolder: '${{ parameters.genai_src }}/ort' + displayName: Copy OnnxRuntime to ort + +- task: DeleteFiles@1 + inputs: + SourceFolder: '${{ parameters.genai_src }}/ort/lib' + Contents: '*tensorrt*' + RemoveSourceFolder: false + displayName: 'Remove tensorrt from lib' + continueOnError: true \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/set-nightly-build-option-variable-step.yml b/.pipelines/stages/jobs/steps/utils/set-nightly-build-option-variable.yml similarity index 100% rename from .pipelines/stages/jobs/steps/set-nightly-build-option-variable-step.yml rename to .pipelines/stages/jobs/steps/utils/set-nightly-build-option-variable.yml diff --git a/.pipelines/stages/nuget-packaging-stage.yml b/.pipelines/stages/nuget-packaging-stage.yml index a72d28a78..e1125cf96 100644 --- a/.pipelines/stages/nuget-packaging-stage.yml +++ b/.pipelines/stages/nuget-packaging-stage.yml @@ -1,20 +1,18 @@ parameters: - name: enable_win_cpu type: boolean -- name: enable_win_gpu - type: boolean -- name: enable_win_arm +- name: enable_win_cuda type: boolean - name: enable_linux_cpu type: boolean -- name: enable_linux_gpu - type: boolean -- name: enable_linux_arm +- name: enable_linux_cuda type: boolean - name: ort_version type: string - name: cuda_version type: string + default: '' + stages: - stage: nuget_packaging @@ -22,35 +20,27 @@ stages: - ${{ if eq(parameters.enable_win_cpu, true) }}: - template: jobs/nuget-win-packaging-job.yml parameters: - cuda_version: ${{ parameters.cuda_version }} - ort_version: ${{ parameters.ort_version }} arch: 'x64' ep: 'cpu' - - ${{ if eq(parameters.enable_win_gpu, true) }}: + ort_version: ${{ parameters.ort_version }} + - ${{ if eq(parameters.enable_win_cuda, true) }}: - template: jobs/nuget-win-packaging-job.yml parameters: + arch: 'x64' cuda_version: ${{ parameters.cuda_version }} + ep: 'cuda' ort_version: ${{ parameters.ort_version }} + + - ${{ if eq(parameters.enable_linux_cpu, true) }}: + - template: jobs/nuget-linux-packaging-job.yml + parameters: arch: 'x64' - ep: 'gpu' - - ${{ if eq(parameters.enable_win_arm, true) }}: - - template: jobs/nuget-win-packaging-job.yml + ep: 'cpu' + ort_version: ${{ parameters.ort_version }} + - ${{ if eq(parameters.enable_linux_cuda, true) }}: + - template: jobs/nuget-linux-packaging-job.yml parameters: + arch: 'x64' cuda_version: ${{ parameters.cuda_version }} - ort_version: ${{ parameters.ort_version }} - arch: 'arm64' - ep: 'cpu' - -# - ${{ if eq(parameters.enable_linux_cpu, true) }}: -# - template: jobs/nuget-linux-cpu-packaging-job.yml -# parameters: -# ort_version: ${{ parameters.ort_version }} -# - ${{ if eq(parameters.enable_linux_gpu, true) }}: -# - template: jobs/nuget-linux-gpu-packaging-job.yml -# parameters: -# ort_version: ${{ parameters.ort_version }} -# - ${{ if eq(parameters.enable_linux_arm, true) }}: -# - template: jobs/nuget-linux-cpu-packaging-job.yml -# parameters: -# arch: 'arm64' -# ort_version: ${{ parameters.ort_version }} \ No newline at end of file + ep: 'cuda' + ort_version: ${{ parameters.ort_version }} \ No newline at end of file diff --git a/.pipelines/stages/py-packaging-stage.yml b/.pipelines/stages/py-packaging-stage.yml index 8e4cecbf0..62181490a 100644 --- a/.pipelines/stages/py-packaging-stage.yml +++ b/.pipelines/stages/py-packaging-stage.yml @@ -1,50 +1,49 @@ parameters: - name: enable_win_cpu type: boolean -- name: enable_win_gpu - type: boolean -- name: enable_win_arm +- name: enable_win_cuda type: boolean - name: enable_linux_cpu type: boolean -- name: enable_linux_gpu - type: boolean -- name: enable_linux_arm +- name: enable_linux_cuda type: boolean - name: ort_version type: string - +- name: cuda_version + type: string + default: '' stages: -- stage: py_packaging +- stage: Python_Packaging_Stage jobs: - ${{ if eq(parameters.enable_win_cpu, true) }}: - - template: jobs/py-win-cpu-packaging-job.yml + - template: jobs/py-win-packaging-job.yml parameters: + arch: 'x64' + ep: 'cpu' ort_version: ${{ parameters.ort_version }} - - ${{ if eq(parameters.enable_win_gpu, true) }}: - - template: jobs/py-win-gpu-packaging-job.yml + - ${{ if eq(parameters.enable_win_cuda, true) }}: + - template: jobs/py-win-packaging-job.yml parameters: - ort_version: ${{ parameters.ort_version }} - - ${{ if eq(parameters.enable_win_arm, true) }}: - - template: jobs/py-win-cpu-packaging-job.yml - parameters: - arch: 'arm64' + arch: 'x64' + cuda_version: ${{ parameters.cuda_version }} + ep: 'cuda' ort_version: ${{ parameters.ort_version }} - ${{ if eq(parameters.enable_linux_cpu, true) }}: - - template: jobs/py-linux-cpu-packaging-job.yml - parameters: - ort_version: ${{ parameters.ort_version }} - - ${{ if eq(parameters.enable_linux_gpu, true) }}: - - template: jobs/py-linux-gpu-packaging-job.yml + - template: jobs/py-linux-packaging-job.yml parameters: + arch: 'x64' + ep: 'cpu' ort_version: ${{ parameters.ort_version }} - - ${{ if eq(parameters.enable_linux_arm, true) }}: - - template: jobs/py-linux-cpu-packaging-job.yml + - ${{ if eq(parameters.enable_linux_cuda, true) }}: + - template: jobs/py-linux-packaging-job.yml parameters: - arch: 'arm64' + arch: 'x64' + cuda_version: ${{ parameters.cuda_version }} + ep: 'cuda' ort_version: ${{ parameters.ort_version }} + diff --git a/VERSION_INFO b/VERSION_INFO index 7fe3ca750..7190fc3db 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1.0rc2 \ No newline at end of file +0.1.0rc3 \ No newline at end of file diff --git a/benchmark/python/README b/benchmark/python/README index 428408332..da1174309 100644 --- a/benchmark/python/README +++ b/benchmark/python/README @@ -10,4 +10,4 @@ Prerequisites: Example call to benchmarking script -python benchmark_e2e.py -i {model folder} -b 1 -l 128 -g 256 -r 100 -w 10 -k 5 -o {output csv file name} -ep cuda \ No newline at end of file +python benchmark_e2e.py -i {model folder} -b 1 -l 128 -g 256 -r 100 -w 10 -k 5 -o {output csv file name} \ No newline at end of file diff --git a/benchmark/python/benchmark_e2e.py b/benchmark/python/benchmark_e2e.py index c267cf590..4dddded46 100644 --- a/benchmark/python/benchmark_e2e.py +++ b/benchmark/python/benchmark_e2e.py @@ -58,9 +58,9 @@ def main(args): # Get tokenizer, and model if args.verbose: print(f"Loading model... ") - model=og.Model(f'{args.input_folder}', og.DeviceType.CPU if args.execution_provider == 'cpu' else og.DeviceType.CUDA) + model=og.Model(f'{args.input_folder}') if args.verbose: print("Model loaded") - tokenizer = model.create_tokenizer() + tokenizer = og.Tokenizer(model) # Generate prompt prompt = [generate_prompt(model, tokenizer, prompt_length)] * batch_size @@ -191,7 +191,6 @@ def main(args): parser.add_argument('-k', '--top_k', type=int, default=50, help='Top k tokens to sample from') parser.add_argument('-p', '--top_p', type=float, default=1.0, help='Top p probability to sample with') parser.add_argument('-o', '--output', type=str, default='genai_e2e', help='Output CSV file name or path (with .csv extension)') - parser.add_argument('-ep', '--execution_provider', type=str, choices=['cpu', 'cuda'], default='cpu', help='Execution provider (device) to use, default is CPU, use CUDA for GPU') parser.add_argument('-v', '--verbose', action='store_true', help='Print extra information') parser.add_argument('-mo', '--print_model_output', action='store_true', help='Print model output') args = parser.parse_args() diff --git a/cmake/presets/CMakeLinuxClangConfigPresets.json b/cmake/presets/CMakeLinuxClangConfigPresets.json index bc0c282f1..59c10d88a 100644 --- a/cmake/presets/CMakeLinuxClangConfigPresets.json +++ b/cmake/presets/CMakeLinuxClangConfigPresets.json @@ -41,7 +41,7 @@ "displayName": "linux clang cpu minsizerel asan", "inherits": [ "linux_clang_default", - "linux_MinSizeRel_default" + "linux_minsizerel_default" ], "binaryDir": "${sourceDir}/build/clang_cpu/minsizerel", "cacheVariables": {} @@ -78,7 +78,7 @@ "displayName": "linux clang cpu minsizerel", "inherits": [ "linux_clang_default", - "linux_MinSizeRel_default" + "linux_minsizerel_default" ], "binaryDir": "${sourceDir}/build/clang_cpu/minsizerel", "cacheVariables": {} diff --git a/cmake/presets/CMakeLinuxDefaultConfigPresets.json b/cmake/presets/CMakeLinuxDefaultConfigPresets.json index 24dd652d1..9e904dc10 100644 --- a/cmake/presets/CMakeLinuxDefaultConfigPresets.json +++ b/cmake/presets/CMakeLinuxDefaultConfigPresets.json @@ -5,10 +5,10 @@ "name": "linux_gcc_default", "generator": "Ninja", "cacheVariables": { - "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack", - "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack", - "CMAKE_SHARED_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack", "CMAKE_POSITION_INDEPENDENT_CODE": "ON", + "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-z,now", + "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-z,now", + "CMAKE_SHARED_LINKER_FLAGS_INIT": "-Wl,-z,now", "USE_CUDA": "OFF" }, "environment": { @@ -22,13 +22,16 @@ } }, { - "name": "linux_clang_default", + "name": "linux_gcc_cuda_default", "inherits": "linux_gcc_default", "cacheVariables": { - "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -L\\usr\\lib64\\x86_64-unknown-linux-gnu", - "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -L\\usr\\lib64\\x86_64-unknown-linux-gnu", - "CMAKE_SHARED_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -L\\usr\\lib64\\x86_64-unknown-linux-gnu" - }, + "USE_CUDA": "ON", + "CMAKE_CUDA_ARCHITECTURES": "60;61;70;75;80;86" + } + }, + { + "name": "linux_clang_default", + "inherits": "linux_gcc_default", "environment": { "CC": "clang", "CXX": "clang++" @@ -36,7 +39,6 @@ }, { "name": "linux_gcc_asan_default", - "inherits": "linux_gcc_default", "cacheVariables": { "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address", "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address", @@ -45,7 +47,6 @@ }, { "name": "linux_clang_asan_default", - "inherits": "linux_gcc_default", "cacheVariables": { "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address -L\\usr\\lib64\\x86_64-unknown-linux-gnu", "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address -L\\usr\\lib64\\x86_64-unknown-linux-gnu", @@ -56,7 +57,7 @@ "name": "linux_release_default", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", - "CMAKE_C_FLAGS": "-DNDEBUG -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -O3 -pipe", + "CMAKE_C_FLAGS": "-DNDEBUG -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -O3 -pipe ", "CMAKE_CXX_FLAGS": "-DNDEBUG -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -O3 -pipe" } }, @@ -77,7 +78,7 @@ } }, { - "name": "linux_MinSizeRel_default", + "name": "linux_minsizerel_default", "cacheVariables": { "CMAKE_BUILD_TYPE": "MinSizeRel", "CMAKE_C_FLAGS": "-DNDEBUG -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -Os -pipe -ggdb3", diff --git a/cmake/presets/CMakeLinuxGccConfigPresets.json b/cmake/presets/CMakeLinuxGccConfigPresets.json index f7725516e..4e7b45a4a 100644 --- a/cmake/presets/CMakeLinuxGccConfigPresets.json +++ b/cmake/presets/CMakeLinuxGccConfigPresets.json @@ -8,6 +8,7 @@ "name": "linux_gcc_cpu_release_asan", "displayName": "linux gcc cpu release asan", "inherits": [ + "linux_gcc_asan_default", "linux_gcc_default", "linux_release_default" ], @@ -18,6 +19,7 @@ "displayName": "linux gcc cpu debug asan", "inherits": [ "linux_gcc_asan_default", + "linux_gcc_default", "linux_debug_default" ], "binaryDir": "${sourceDir}/build/gcc_cpu/debug" @@ -26,6 +28,7 @@ "name": "linux_gcc_cpu_relwithdebinfo_asan", "displayName": "linux gcc cpu relwithdebinfo asan", "inherits": [ + "linux_gcc_asan_default", "linux_gcc_default", "linux_relwithdebinfo_default" ], @@ -35,8 +38,9 @@ "name": "linux_gcc_cpu_minsizerel_asan", "displayName": "linux gcc cpu minsizerel asan", "inherits": [ + "linux_gcc_asan_default", "linux_gcc_default", - "linux_MinSizeRel_default" + "linux_minsizerel_default" ], "binaryDir": "${sourceDir}/build/gcc_cpu/minsizerel" }, @@ -54,7 +58,7 @@ "displayName": "linux gcc cpu debug", "inherits": [ "linux_gcc_default", - "linux_release_default" + "linux_debug_default" ], "binaryDir": "${sourceDir}/build/gcc_cpu/debug" }, @@ -72,7 +76,7 @@ "displayName": "linux gcc cpu minsizerel", "inherits": [ "linux_gcc_default", - "linux_MinSizeRel_default" + "linux_minsizerel_default" ], "binaryDir": "${sourceDir}/build/gcc_cpu/minsizerel" }, @@ -81,96 +85,76 @@ "displayName": "linux gcc cuda release asan", "inherits": [ "linux_gcc_asan_default", + "linux_gcc_cuda_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/release", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/release" }, { "name": "linux_gcc_cuda_debug_asan", "displayName": "linux gcc cuda debug asan", "inherits": [ "linux_gcc_asan_default", + "linux_gcc_cuda_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/debug", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/debug" }, { "name": "linux_gcc_cuda_relwithdebinfo_asan", "displayName": "linux gcc cuda relwithdebinfo asan", "inherits": [ "linux_gcc_asan_default", + "linux_gcc_cuda_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/relwithdebinfo", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/relwithdebinfo" }, { "name": "linux_gcc_cuda_minsizerel_asan", "displayName": "linux gcc cuda minsizerel asan", "inherits": [ "linux_gcc_asan_default", - "linux_MinSizeRel_default" + "linux_gcc_cuda_default", + "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/minsizerel", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/minsizerel" }, { "name": "linux_gcc_cuda_release", "displayName": "linux gcc cuda release", "inherits": [ - "linux_gcc_default", + "linux_gcc_cuda_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/release", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/release" }, { "name": "linux_gcc_cuda_debug", "displayName": "linux gcc cuda debug", "inherits": [ - "linux_gcc_default", + "linux_gcc_cuda_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/debug", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/debug" }, { "name": "linux_gcc_cuda_relwithdebinfo", "displayName": "linux gcc cuda relwithdebinfo", "inherits": [ - "linux_gcc_default", + "linux_gcc_cuda_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/relwithdebinfo", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/relwithdebinfo" }, { "name": "linux_gcc_cuda_minsizerel", "displayName": "linux gcc cuda minsizerel", "inherits": [ - "linux_gcc_default", - "linux_MinSizeRel_default" + "linux_gcc_cuda_default", + "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/minsizerel", - "cacheVariables": { - "USE_CUDA": "ON" - } + "binaryDir": "${sourceDir}/build/gcc_cuda/minsizerel" } ] } \ No newline at end of file diff --git a/cmake/presets/CMakeWinConfigPresets.json b/cmake/presets/CMakeWinConfigPresets.json index 84e1dfe5b..be70ca2d6 100644 --- a/cmake/presets/CMakeWinConfigPresets.json +++ b/cmake/presets/CMakeWinConfigPresets.json @@ -18,8 +18,15 @@ } }, { - "name": "windows_release_default", + "name": "windows_cuda_default", "inherits": "windows_cpu_default", + "cacheVariables": { + "USE_CUDA": "ON", + "CMAKE_CUDA_ARCHITECTURES": "60;61;70;75;80;86" + } + }, + { + "name": "windows_release_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG" @@ -27,7 +34,6 @@ }, { "name": "windows_debug_default", - "inherits": "windows_cpu_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1" @@ -35,7 +41,6 @@ }, { "name": "windows_relwithdebinfo_default", - "inherits": "windows_cpu_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG" @@ -43,7 +48,6 @@ }, { "name": "windows_minsizerel_default", - "inherits": "windows_cpu_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG" @@ -51,7 +55,6 @@ }, { "name": "windows_release_asan_default", - "inherits": "windows_release_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG /fsanitize=address" @@ -59,7 +62,6 @@ }, { "name": "windows_debug_asan_default", - "inherits": "windows_debug_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1 /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1 /fsanitize=address" @@ -67,7 +69,6 @@ }, { "name": "windows_relwithdebinfo_asan_default", - "inherits": "windows_relwithdebinfo_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG /fsanitize=address" @@ -75,7 +76,6 @@ }, { "name": "windows_minsizerel_asan_default", - "inherits": "windows_minsizerel_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG /fsanitize=address" @@ -83,55 +83,82 @@ }, { "name": "windows_x64_cpu_release_asan", - "inherits": "windows_release_asan_default", + "inherits": [ + "windows_release_asan_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu release asan", "binaryDir": "${sourceDir}/build/release/cpu_asan" }, { "name": "windows_x64_cpu_debug_asan", - "inherits": "windows_debug_asan_default", + "inherits": [ + "windows_debug_asan_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu debug asan", "binaryDir": "${sourceDir}/build/debug/cpu_asan" }, { "name": "windows_x64_cpu_relwithdebinfo_asan", - "inherits": "windows_relwithdebinfo_asan_default", + "inherits": [ + "windows_relwithdebinfo_asan_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu relwithdebinfo asan", "binaryDir": "${sourceDir}/build/relwithdebinfo/cpu_asan" }, { "name": "windows_x64_cpu_minsizerel_asan", - "inherits": "windows_minsizerel_asan_default", + "inherits": [ + "windows_minsizerel_asan_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu minsizerel asan", "binaryDir": "${sourceDir}/build/minsizerel/cpu_asan" }, { "name": "windows_x64_cpu_release", - "inherits": "windows_release_default", + "inherits": [ + "windows_release_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu release", "binaryDir": "${sourceDir}/build/release/cpu_default" }, { "name": "windows_x64_cpu_debug", - "inherits": "windows_debug_default", + "inherits": [ + "windows_debug_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu debug", "binaryDir": "${sourceDir}/build/debug/cpu_default" }, { "name": "windows_x64_cpu_relwithdebinfo", - "inherits": "windows_relwithdebinfo_default", + "inherits": [ + "windows_relwithdebinfo_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu relwithdebinfo", "binaryDir": "${sourceDir}/build/relwithdebinfo/cpu_default" }, { "name": "windows_x64_cpu_minsizerel", - "inherits": "windows_minsizerel_default", + "inherits": [ + "windows_minsizerel_default", + "windows_cpu_default" + ], "displayName": "windows x64 cpu minsizerel", "binaryDir": "${sourceDir}/build/minsizerel/cpu_default" }, { "name": "windows_x64_cuda_release_asan", - "inherits": "windows_release_asan_default", + "inherits": [ + "windows_release_asan_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda release asan", "binaryDir": "${sourceDir}/build/release/cuda_asan", "cacheVariables": { @@ -140,7 +167,10 @@ }, { "name": "windows_x64_cuda_debug_asan", - "inherits": "windows_debug_asan_default", + "inherits": [ + "windows_debug_asan_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda debug asan", "binaryDir": "${sourceDir}/build/debug/cuda_asan", "cacheVariables": { @@ -149,7 +179,10 @@ }, { "name": "windows_x64_cuda_relwithdebinfo_asan", - "inherits": "windows_relwithdebinfo_asan_default", + "inherits": [ + "windows_relwithdebinfo_asan_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda relwithdebinfo asan", "binaryDir": "${sourceDir}/build/relwithdebinfo/cuda_asan", "cacheVariables": { @@ -158,7 +191,10 @@ }, { "name": "windows_x64_cuda_minsizerel_asan", - "inherits": "windows_release_asan_default", + "inherits": [ + "windows_minsizerel_asan_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda minsizerel asan", "binaryDir": "${sourceDir}/build/minsizerel/cuda_asan", "cacheVariables": { @@ -167,7 +203,10 @@ }, { "name": "windows_x64_cuda_release", - "inherits": "windows_release_default", + "inherits": [ + "windows_release_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda release", "binaryDir": "${sourceDir}/build/release/cuda_default", "cacheVariables": { @@ -176,7 +215,10 @@ }, { "name": "windows_x64_cuda_debug", - "inherits": "windows_debug_default", + "inherits": [ + "windows_debug_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda debug", "binaryDir": "${sourceDir}/build/debug/cuda_default", "cacheVariables": { @@ -185,7 +227,10 @@ }, { "name": "windows_x64_cuda_relwithdebinfo", - "inherits": "windows_relwithdebinfo_default", + "inherits": [ + "windows_relwithdebinfo_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda relwithdebinfo", "binaryDir": "${sourceDir}/build/relwithdebinfo/cuda_default", "cacheVariables": { @@ -194,7 +239,10 @@ }, { "name": "windows_x64_cuda_minsizerel", - "inherits": "windows_minsizerel_default", + "inherits": [ + "windows_minsizerel_default", + "windows_cuda_default" + ], "displayName": "windows x64 cuda minsizerel", "binaryDir": "${sourceDir}/build/minsizerel/cuda_default", "cacheVariables": { diff --git a/examples/phi2/c/CMakeLists.txt b/examples/c/CMakeLists.txt similarity index 100% rename from examples/phi2/c/CMakeLists.txt rename to examples/c/CMakeLists.txt diff --git a/examples/phi2/c/README.md b/examples/c/README.md similarity index 97% rename from examples/phi2/c/README.md rename to examples/c/README.md index 4d2179519..6bd58b38d 100644 --- a/examples/phi2/c/README.md +++ b/examples/c/README.md @@ -1,4 +1,4 @@ -# Phi-2 +# Gen-AI C Phi-2 Example ## Install the onnxruntime-genai library diff --git a/examples/phi2/c/include/.gitkeep b/examples/c/include/.gitkeep similarity index 100% rename from examples/phi2/c/include/.gitkeep rename to examples/c/include/.gitkeep diff --git a/examples/phi2/c/lib/.gitkeep b/examples/c/lib/.gitkeep similarity index 100% rename from examples/phi2/c/lib/.gitkeep rename to examples/c/lib/.gitkeep diff --git a/examples/phi2/c/src/main.cpp b/examples/c/src/main.cpp similarity index 100% rename from examples/phi2/c/src/main.cpp rename to examples/c/src/main.cpp diff --git a/examples/phi2/csharp/HelloPhi2.csproj b/examples/csharp/HelloPhi2.csproj similarity index 100% rename from examples/phi2/csharp/HelloPhi2.csproj rename to examples/csharp/HelloPhi2.csproj diff --git a/examples/phi2/csharp/HelloPhi2.sln b/examples/csharp/HelloPhi2.sln similarity index 100% rename from examples/phi2/csharp/HelloPhi2.sln rename to examples/csharp/HelloPhi2.sln diff --git a/examples/phi2/csharp/Program.cs b/examples/csharp/Program.cs similarity index 93% rename from examples/phi2/csharp/Program.cs rename to examples/csharp/Program.cs index e2374690a..e70920cc0 100644 --- a/examples/phi2/csharp/Program.cs +++ b/examples/csharp/Program.cs @@ -6,7 +6,7 @@ Console.WriteLine("-------------"); string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "phi-2"); -using Model model = new Model(modelPath, DeviceType.CPU); +using Model model = new Model(modelPath); using Tokenizer tokenizer = new Tokenizer(model); while (true) diff --git a/examples/phi2/csharp/README.md b/examples/csharp/README.md similarity index 96% rename from examples/phi2/csharp/README.md rename to examples/csharp/README.md index 1dccfa687..d6b4010f3 100644 --- a/examples/phi2/csharp/README.md +++ b/examples/csharp/README.md @@ -1,4 +1,4 @@ -# Phi-2 +# Gen-AI C# Phi-2 Example ## Install the onnxruntime-genai library diff --git a/examples/gemma/README.md b/examples/gemma/README.md deleted file mode 100644 index 640fb4de2..000000000 --- a/examples/gemma/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Gemma - -## Clone this repo - -```bash -git clone https://github.com/microsoft/onnxruntime-genai.git -cd onnxruntime-genai -``` - -## Install the onnxruntime-genai library - -* (Temporary) Build the library according to [the build instructions](../../README.md#build-from-source) - -* Install the python package - - ```bash - cd build/wheel - pip install onnxruntime-genai-*.whl - ``` - - -## Get the model - -Install the model builder script dependencies - -```bash -pip install numpy -pip install transformers -pip install torch -pip install onnx -pip install onnxruntime -``` - -Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../src/python/py/models/README.md) - -```bash -cd examples/gemma -python -m onnxruntime_genai.models.builder -m google/gemma-2b -e cuda -p fp16 -o ./example-models/gemma-2b-cuda -``` - -## Run gemma - -```bash -python gemma.py -``` \ No newline at end of file diff --git a/examples/gemma/gemma-loop.py b/examples/gemma/gemma-loop.py deleted file mode 100644 index a62c95f9c..000000000 --- a/examples/gemma/gemma-loop.py +++ /dev/null @@ -1,36 +0,0 @@ -import onnxruntime_genai as og - -print("Loading model...") - -# The first argument is the name of the folder containing the model files -model=og.Model("./example-models/gemma-2b-cuda", og.DeviceType.CUDA) -print("Model loaded") -tokenizer=og.Tokenizer(model) -print("Tokenizer created") - -prompts = ["I like walking my cute dog", - "What is the best restaurant in town?", - "Hello, how are you today?"] - -input_tokens = tokenizer.encode_batch(prompts) - -params=og.GeneratorParams(model) -params.set_search_options({"max_length":64}) -params.input_ids=input_tokens - - -generator=og.Generator(model, params) - -print("Generator created") - -print("Running generation loop ...") - -while not generator.is_done(): - generator.compute_logits() - generator.generate_next_token_top() - -print("Outputs:") - -for i in range(len(prompts)): - print(tokenizer.decode(generator.get_sequence(i))) - print() diff --git a/examples/gemma/gemma.py b/examples/gemma/gemma.py deleted file mode 100644 index 5d8f7c257..000000000 --- a/examples/gemma/gemma.py +++ /dev/null @@ -1,29 +0,0 @@ -import onnxruntime_genai as og -import time - -print("Loading model...") -model=og.Model("./example-models/gemma-2b-cuda", og.DeviceType.CUDA) -print("Model loaded") -tokenizer=og.Tokenizer(model) -print("Tokenizer created") - - -# Keep asking for input prompts in an loop -while True: - text = input("Input:") - input_tokens = tokenizer.encode(text) - - params=og.GeneratorParams(model) - params.set_search_options({"max_length":256}) - params.input_ids = input_tokens - - start_time=time.time() - output_tokens=model.generate(params)[0] - run_time=time.time()-start_time - print(f"Tokens: {len(output_tokens)}, Time: {run_time:.2f}, Tokens per second: {len(output_tokens)/run_time:.2f}") - - print("Output:") - print(tokenizer.decode(output_tokens)) - - print() - print() diff --git a/examples/llama/README.md b/examples/llama/README.md deleted file mode 100644 index db00974b4..000000000 --- a/examples/llama/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Llama - -## Clone this repo - -```bash -git clone https://github.com/microsoft/onnxruntime-genai.git -cd onnxruntime-genai -``` - -## Install the onnxruntime-genai library - -* (Temporary) Build the library according to [the build instructions](../../README.md#build-from-source) - -* Install the python package - - ```bash - cd build/wheel - pip install onnxruntime-genai-*.whl - ``` - - -## Get the model - -Install the model builder script dependencies - -```bash -pip install numpy -pip install transformers -pip install torch -pip install onnx -pip install onnxruntime -``` - -Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../src/python/py/models/README.md) - -```bash -cd examples/llama -python -m onnxruntime_genai.models.builder -m meta-llama/Llama-2-7b-chat-hf -e cpu -p int4 -o ./example-models/llama2-7b-chat-int4-cpu -``` - -## Run Llama - -```bash -python llama.py -``` \ No newline at end of file diff --git a/examples/llama/llama-loop.py b/examples/llama/llama-loop.py deleted file mode 100644 index ffa3d2fbc..000000000 --- a/examples/llama/llama-loop.py +++ /dev/null @@ -1,36 +0,0 @@ -import onnxruntime_genai as og - -print("Loading model...") - -# The first argument is the name of the folder containing the model files -model=og.Model("example-models/llama2-7b-chat-int4-cpu", og.DeviceType.CPU) -print("Model loaded") -tokenizer=og.Tokenizer(model) -print("Tokenizer created") - -prompts = ["I like walking my cute dog", - "What is the best restaurant in town?", - "Hello, how are you today?"] - -input_tokens = tokenizer.encode_batch(prompts) - -params=og.GeneratorParams(model) -params.set_search_options({"max_length":256}) -params.input_ids=input_tokens - - -generator=og.Generator(model, params) - -print("Generator created") - -print("Running generation loop ...") - -while not generator.is_done(): - generator.compute_logits() - generator.generate_next_token_top_p(0.9, 1.0) - -print("Outputs:") - -for i in range(len(prompts)): - print(tokenizer.decode(generator.get_sequence(i))) - print() diff --git a/examples/llama/llama.py b/examples/llama/llama.py deleted file mode 100644 index 3f8a23e7d..000000000 --- a/examples/llama/llama.py +++ /dev/null @@ -1,31 +0,0 @@ -import onnxruntime_genai as og -import time - -print("Loading model...") - -# The first argument is the name of the folder containing the model files -model=og.Model("example-models/llama2-7b-chat-int4-cpu", og.DeviceType.CPU) -print("Model loaded") -tokenizer=og.Tokenizer(model) -print("Tokenizer created") - - -# Keep asking for input prompts in an loop -while True: - text = input("Input:") - input_tokens = tokenizer.encode(text) - - params=og.GeneratorParams(model) - params.set_search_options({"max_length":256}) - params.input_ids = input_tokens - - start_time=time.time() - output_tokens=model.generate(params)[0] - run_time=time.time()-start_time - print(f"Tokens: {len(output_tokens)} Time: {run_time:.2f} Tokens per second: {len(output_tokens)/run_time:.2f}") - - print("Output:") - print(tokenizer.decode(output_tokens)) - - print() - print() diff --git a/examples/mistral/README.md b/examples/mistral/README.md deleted file mode 100644 index a520aa6b2..000000000 --- a/examples/mistral/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Mistral - -## Clone this repo - -```bash -git clone https://github.com/microsoft/onnxruntime-genai.git -cd onnxruntime-genai -``` - -## Install the onnxruntime-genai library - -* (Temporary) Build the library according to [the build instructions](../../README.md#build-from-source) - -* Install the python package - - ```bash - cd build/wheel - pip install onnxruntime-genai-*.whl - ``` - - -## Get the model - -Install the model builder script dependencies - -```bash -pip install numpy -pip install transformers -pip install torch -pip install onnx -pip install onnxruntime -``` - -Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../src/python/py/models/README.md) - -```bash -cd examples/mistral -python -m onnxruntime_genai.models.builder -m mistralai/Mistral-7B-v0.1 -e cuda -p fp16 -o ./example-models/mistral-7b-fp16-cuda -``` - -## Run Mistral - -```bash -python mistral-loop.py -``` \ No newline at end of file diff --git a/examples/mistral/mistral-loop.py b/examples/mistral/mistral-loop.py deleted file mode 100644 index 21d36fa80..000000000 --- a/examples/mistral/mistral-loop.py +++ /dev/null @@ -1,119 +0,0 @@ -import onnxruntime_genai as og - -print("Loading model...") - -# The first argument is the name of the folder containing the model files -model = og.Model("example-models/mistral-7b-fp16-cuda", og.DeviceType.CUDA) -print("Model loaded") -tokenizer = og.Tokenizer(model) -print("Tokenizer created") - -# Note: Prompt was auto-generated by Copilot. Visit `copilot.microsoft.com` to try it out. -prompts = ["""In the heart of the ancient forest, where sunlight filtered through the dense canopy, there stood an old oak tree. Its gnarled roots clung to the earth, and its branches reached skyward like arthritic fingers. The villagers whispered tales of the tree's magic—a guardian of secrets, a keeper of forgotten memories. -One moonlit night, as the wind whispered secrets through the leaves, a young girl named Elowen ventured into the forest. Her grandmother's stories echoed in her mind—the oak tree held the key to unlocking her family's past. Elowen carried a small leather-bound book, its pages filled with cryptic symbols and faded ink. It was her grandmother's journal, passed down through generations. -Elowen approached the ancient oak, its bark etched with symbols that seemed to pulse with energy. She pressed her palm against the rough surface, feeling a surge of warmth. The tree stirred, its branches rustling like a thousand whispered secrets. Elowen closed her eyes and whispered her grandmother's name. -Suddenly, the air shimmered, and the oak split open, revealing a hidden chamber within its trunk. Elowen stepped inside, her breath catching as she beheld the treasures within. Ancient scrolls, delicate vials of starlight, and a silver locket—all waiting to reveal their secrets. -As Elowen explored the chamber, she realized that the oak tree was more than a guardian; it was a bridge between worlds. Each item held a memory, a piece of her family's history. She unclasped the silver locket, and a vision flooded her mind—a woman with eyes like the forest, standing beneath the same oak tree, her laughter carried by the wind. -Elowen's heart raced. Who was this woman? What had she lost? And why had her grandmother kept these memories hidden? The answers lay within the ancient oak, waiting for Elowen to unravel their threads. -And so, under the moon's watchful gaze, Elowen vowed to uncover the truth. She would decipher the symbols, unlock the vials of starlight, and follow the whispers of the wind. For the old oak tree held not only her family's past but also the promise of a future filled with magic and wonder. -Elowen spent countless nights within the hidden chamber of the ancient oak. Each artifact whispered its story to her—a symphony of memories woven into the very fabric of the tree. The silver locket revealed a forbidden love, the starlight vials held dreams of distant constellations, and the scrolls bore forgotten spells. -As Elowen deciphered the cryptic symbols in her grandmother’s journal, she discovered that her family was more than mortal. They were guardians of the in-between, tasked with maintaining the delicate balance between the earthly realm and the mystical one. The oak tree served as their conduit, its roots reaching deep into the earth and its branches touching the stars. -One moonless night, Elowen traced her lineage back through the generations. She found her great-great-grandmother’s name—Seraphina—etched into the oldest scroll. Seraphina had been a seer, her eyes reflecting the secrets of the cosmos. She had danced with spirits, whispered to the wind, and fallen in love with a celestial being—a star that had descended to the forest. -The star’s name was Lyra, and their love was forbidden. The heavens wept when they embraced, and the earth trembled with longing. But fate was unkind. Lyra was bound to the sky, and Seraphina to the oak. Their union was a fragile thread connecting realms, and it threatened to unravel the very fabric of existence. -Elowen’s heart ached for her ancestors. She wondered if their love had been worth the sacrifice—their memories hidden within the oak, their whispers carried by the wind. She yearned to reunite them, to mend the fractured bond between earth and sky. -Guided by her grandmother’s journal, Elowen embarked on a quest. She climbed the highest peaks, seeking ancient altars where starlight met soil. She collected dewdrops from spiderwebs, tears shed by the moon. And at the heart of a forgotten glade, she found the Celestial Pool—a mirror reflecting the heavens. -Elowen dipped the silver locket into the pool, its surface rippling like stardust. She whispered Lyra’s name, and the water shimmered. A figure materialized—an ethereal being with eyes like galaxies. It was Lyra, still bound to the sky but yearning for Seraphina. -“Elowen,” Lyra’s voice echoed, “you carry the blood of seers. You can bridge our worlds.” -Elowen’s resolve hardened. She would reunite the lovers, even if it meant unraveling reality itself. She gathered the vials of starlight, the forgotten spells, and climbed the ancient oak. Its branches quivered, sensing her purpose. -At the highest bough, Elowen chanted the incantation—a melody woven from memories, love, and sacrifice. The oak split open, revealing a portal—a bridge of light connecting earth and sky. Elowen stepped through, her grandmother’s journal clutched to her chest. -In the celestial realm, she found Seraphina—a wisp of light, her eyes filled with longing. And there, across the expanse, was Lyra—a constellation burning bright. -Elowen held out the silver locket. “Love transcends boundaries,” she declared. “Let this be our legacy.” -The lovers reached for each other, their fingers brushing stardust. The oak trembled, and reality quivered. Elowen closed her eyes, feeling the threads of existence weave anew. -When she opened them, the ancient oak stood whole, its bark etched with new symbols—a testament to love’s enduring magic. And in the heart of the forest, sunlight filtered through the dense canopy, illuminating the gnarled roots and arthritic branches. -Elowen smiled. The whispers of the wind carried a new tale—one of sacrifice, reunion, and the unbreakable bond between earth and sky. -And so, the old oak tree remained—a guardian of secrets, a keeper of forgotten memories, and a bridge between worlds. -In the heart of the bustling city, where neon signs flickered and commuters hurried along crowded sidewalks, there existed a hidden café. Its entrance was unassuming—a weathered wooden door tucked between a high-rise office building and a vintage record store. The sign above read "The Midnight Brew," its letters painted in midnight blue. -Those who stumbled upon the café often did so by accident. Perhaps they took a wrong turn while searching for the latest trendy coffee shop or lost track of time during a late-night walk. But once inside, they discovered a world beyond the ordinary. -The interior was a blend of nostalgia and whimsy. Velvet curtains framed the windows, casting a soft glow on mismatched tables and chairs. The walls were adorned with antique clocks, each showing a different time—some ticking backward, others frozen at midnight. The air smelled of freshly ground coffee beans and secrets waiting to be shared. -The patrons were an eclectic mix—a jazz pianist with ink-stained fingers, a librarian who whispered poetry to her cup, a time traveler nursing a cup of chamomile tea. The barista, a mysterious figure named Elias, wore a bow tie and a perpetual smile. His eyes held stories—of lost love, forgotten dreams, and the passage of centuries. -The menu was equally enchanting. Instead of lattes and cappuccinos, it offered Moonlight Elixirs and Stardust Brews. Each drink had a purpose—a memory revival, a glimpse into parallel worlds, or simply a moment of solace. Regulars knew to order the Timeless Espresso when seeking answers, and the Galaxy Latte when yearning for adventure. -But the true magic of The Midnight Brew lay in its back room—a door marked "Beyond." Few dared to enter, for it led to realms unknown. Some claimed it opened to a moonlit forest, others to a starship hurtling through constellations. Elias would merely smile and say, "Choose your path wisely." -One stormy night, a young woman named Luna pushed open the door. She stepped into a swirling vortex of colors—a kaleidoscope of memories and possibilities. The door closed behind her, and she found herself standing on the edge of a cliff, overlooking a sea of silver mist. -Luna had a choice—to return to the café or explore the unknown. She thought of her mundane life—the deadlines, the routines, the unanswered questions. And then she thought of the jazz pianist's melody, the librarian's whispered verses, and Elias's enigmatic gaze. -With a deep breath, Luna stepped forward, her heart pounding. The mist enveloped her, and she vanished from The Midnight Brew. But her story remained—a legend whispered among the patrons, a reminder that sometimes, the most extraordinary journeys begin with a simple cup of coffee. -Luna stumbled through the swirling mist, her senses reeling. Colors danced around her—cobalt blues, iridescent greens, and shades she couldn’t name. The ground beneath her feet felt both solid and insubstantial, like stepping on moonbeams. -Ahead, a path materialized—a narrow trail bordered by luminous mushrooms. Their soft glow illuminated the way, revealing a forest unlike any Luna had seen. Trees stood tall, their trunks adorned with silver leaves that rustled in a melody only she could hear. Birds with wings of stardust flitted between branches, their songs echoing through the enchanted air. -Luna’s heart raced. She had chosen the unknown, and now she was part of a story woven into the fabric of existence. But what was her purpose here? Why had The Midnight Brew led her to this place? -As she walked, Luna noticed that time flowed differently. Minutes stretched into hours, yet she felt neither hunger nor fatigue. She touched the bark of a tree, and images flooded her mind—a cosmic library where books whispered secrets, a moonlit ballroom where forgotten dances unfolded, and a mirror pool reflecting alternate versions of herself. -The forest held answers, but it also posed questions. Luna encountered other travelers—a star-cloaked wanderer who spoke in riddles, a shadow weaver who wove memories into tapestries, and a timekeeper with eyes like ancient galaxies. Each offered cryptic guidance, urging her to follow her intuition. -One day, Luna reached a clearing—a Celestial Grove where constellations hung low, their patterns shifting. In the center stood a silver pedestal, upon which rested a single crystal goblet filled with shimmering liquid. The goblet seemed to hold the essence of the universe itself. -The star-cloaked wanderer appeared beside her. “Drink,” he said, his voice echoing through the grove. “Choose your destiny.” -Luna hesitated. The liquid sparkled like memories—of her childhood dreams, her lost love, and the unanswered questions that had led her to The Midnight Brew. She dipped her fingers into the goblet, and images swirled—a forgotten lullaby, a doorway of mirrors, and Elias’s enigmatic smile. -“Drink,” the wanderer repeated. “The choice is yours.” -Luna lifted the goblet to her lips. The liquid tasted like moonlight and longing. Visions flooded her—a reunion with her grandmother, the jazz pianist’s melody leading her home, and a final glimpse of The Midnight Brew. -She understood now. The café was a nexus—a place where souls intersected, where time looped backward, and where choices rippled across dimensions. Elias, the barista, was more than he seemed—a guardian of paths, a keeper of choices. -Luna drank deeply, her mind expanding. She saw the threads connecting her mundane life to the cosmic tapestry—the deadlines, the routines, the unanswered questions—all woven into something greater. She glimpsed Seraphina and Lyra, their love transcending realms, and knew that her journey was intertwined with theirs. -When Luna opened her eyes, she stood once again at the entrance of The Midnight Brew. The wooden door beckoned, and Elias awaited behind the counter, his eyes filled with knowing. -“Welcome back,” he said, pouring a cup of Moonlight Elixir. “What did you discover?” -Luna smiled. “That every cup of coffee holds a universe,” she replied. “And that sometimes, the most extraordinary journeys begin with a simple choice.” -And so, Luna returned to her mundane life, but she carried the forest within her—a map of constellations, a taste of moonlight, and the promise of more adventures. The Midnight Brew remained, its wooden door inviting wanderers, dreamers, and those seeking answers. -And somewhere, beyond the veil, Seraphina and Lyra danced—a love story etched into the cosmos, whispered by the wind, and brewed in the heart of midnight. -In a quiet coastal town, where seagulls wheeled overhead and salt-laden breezes carried whispers of forgotten tales, there stood an abandoned lighthouse. Its white paint had peeled, revealing weathered wood beneath. The lantern room, once a beacon guiding ships safely home, now sat empty. -The townspeople spoke of the lighthouse in hushed tones. They said it was haunted—that on moonless nights, a spectral figure could be seen climbing the winding staircase, its footsteps echoing through the hollow tower. -One stormy evening, a young artist named Evelyn arrived in the town. She carried a canvas, her brushes, and a heart heavy with grief. Her sister, a sailor, had vanished at sea, leaving behind only a cryptic note: “Find the light in the darkness.” -Driven by love and desperation, Evelyn sought answers. She wandered the rocky shore, her eyes drawn to the abandoned lighthouse. Its windows were like empty eyes, staring out at the churning waves. -As the moon rose, Evelyn climbed the narrow staircase. Cobwebs clung to her fingers, and the air smelled of salt and decay. At the top, she pushed open the lantern room door. The wind howled, and rain splattered against the glass. -And there, in the center of the room, stood a flickering candle. Its flame danced, casting shadows on the walls. Evelyn approached, her heart racing. -Was this the light her sister had spoken of? Or was it something more—a connection between realms, a bridge to the lost? -Evelyn dipped her brush into the candle’s flame and painted. The canvas absorbed the light, revealing images—a ship caught in a tempest, a sailor clinging to a broken mast, and a lighthouse standing firm. -As the last stroke fell, the spectral figure materialized—a woman with seafoam eyes and hair like tangled seaweed. She reached out, her touch both icy and warm. -“You seek answers,” the ghost whispered. “I am the keeper of memories—the one who guides lost souls.” -Evelyn’s sister appeared beside her, translucent and smiling. “Find the light,” she said. “It leads to hope, to redemption.” -And so, Evelyn vowed to restore the lighthouse. She scraped away the peeling paint, revealing the wood’s hidden strength. She lit the lantern, and its beam cut through the storm. -The townspeople watched in awe as the lighthouse blazed to life. Ships altered course, and sailors found safe harbor. -But Evelyn knew the true purpose—the light not only guided ships but also bridged the gap between the living and the lost. And as she painted, her sister’s laughter echoed through the tower. -For in that quiet coastal town, where seagulls wheeled overhead and salt-laden breezes carried whispers, hope flickered like a candle in the darkness. -Evelyn stood in the lantern room, her brush still warm from the candle’s flame. The spectral figure—the keeper of memories—watched her with eyes that held both sorrow and purpose. -“You seek answers,” the ghost whispered again. “But answers come at a cost.” -Evelyn’s sister, translucent and radiant, stepped closer. “The lighthouse,” she said, “it’s more than a guide for ships. It’s a bridge—a threshold between realms.” -The storm outside intensified, rain lashing against the glass. The sea roared, hungry for lost souls. Evelyn’s heart clenched. She had painted her sister back into existence, but what did it mean? What was the purpose of this spectral reunion? -“Tell me,” Evelyn pleaded. “Why did you vanish? Why leave only a cryptic note?” -Her sister’s laughter echoed—a melody woven into the wind. “Because,” she said, “I found the light.” -Evelyn’s mind raced. The candle, the canvas, the hidden purpose—the pieces clicked together. Her sister had glimpsed something beyond life, something that transcended the mundane. And now, Evelyn stood on the precipice of understanding. -The ghost beckoned. “The lighthouse must be restored,” she said. “Its light binds worlds. But to do so, you must retrieve the lost memories—the fragments scattered across time.” -Evelyn nodded, her resolve firm. “Where do I begin?” -“Within,” the ghost replied. “The lighthouse’s heart.” -Evelyn descended the winding staircase, her footsteps echoing. In the base of the tower, she found a hidden chamber—a Memory Vault. Its shelves held crystal orbs, each containing a fragment of forgotten lives—the laughter of children, the scent of blooming roses, the taste of salt on sun-kissed skin. -She touched an orb, and memories flooded her—the warmth of her sister’s hand, the way they’d danced in rainstorms, the promise to find the light. But other memories surfaced too—of betrayal, of choices made in darkness. -Evelyn collected the orbs, her canvas absorbing their essence. She painted—the ship caught in the tempest, the sailor clinging to the mast, and the lighthouse standing firm. The spectral figure watched, her eyes filled with longing. -“Release them,” the ghost urged. “Let the memories guide you.” -Evelyn stepped outside, the storm still raging. She climbed to the lantern room, her canvas glowing. The lighthouse blazed anew, its beam cutting through rain and mist. Ships altered course, sailors found safe harbor, and lost memories danced in the light. -But Evelyn knew her sister’s truth—the light wasn’t just for ships. It was a beacon for souls—the living and the departed. She touched the spectral figure’s hand, and they merged—a bridge between realms. -The townspeople marveled at the restored lighthouse. They spoke of its magic, its ability to heal hearts and guide lost wanderers. And Evelyn? She painted—capturing sunsets, moonrise, and the whispers of the sea. -One moonless night, she climbed to the lantern room. Her sister stood beside her, both solid and ethereal. “Why?” Evelyn asked. “Why did you find the light?” -Her sister smiled. “To remind you,” she said, “that love transcends time. That memories are threads connecting us all.” -And so, the lighthouse stood—a sentinel of memories, a bridge between realms, and a testament to the light found in the darkest corners of existence. -"""] - -input_tokens = tokenizer.encode_batch(prompts) - -params = og.GeneratorParams(model) -params.set_search_options({"max_length": input_tokens.shape[1] + 256, "repetition_penalty": 3.0}) -params.input_ids = input_tokens - - -generator = og.Generator(model, params) - -print("Generator created") - -print("Running generation loop ...") - -while not generator.is_done(): - generator.compute_logits() - generator.generate_next_token_top() - -print("Outputs:") - -for i in range(len(prompts)): - print(tokenizer.decode(generator.get_sequence(i))) - print() diff --git a/examples/phi2/python/phi2-streaming.py b/examples/phi2/python/phi2-streaming.py deleted file mode 100644 index fc89b55c4..000000000 --- a/examples/phi2/python/phi2-streaming.py +++ /dev/null @@ -1,33 +0,0 @@ -import onnxruntime_genai as og - -print("Loading model...") - -# The first argument is the name of the folder containing the model files -model=og.Model(f'example-models/phi2-int4-cpu', og.DeviceType.CPU) -print("Model loaded") -tokenizer=og.Tokenizer(model) -print("Tokenizer created") - -prompt = '''def print_prime(n): - """ - Print all primes between 1 and n - """''' - -input_tokens = tokenizer.encode(prompt) - -params=og.GeneratorParams(model) -params.set_search_options({"max_length":256}) -params.input_ids = input_tokens - -generator=og.Generator(model, params) -tokenizer_stream=tokenizer.create_stream() - -print("Generator created") - -print("Output:") -print(prompt, end='', flush=True) - -while not generator.is_done(): - generator.compute_logits() - generator.generate_next_token_top_p(0.7, 0.6) - print(tokenizer_stream.decode(generator.get_next_tokens()[0]), end='', flush=True) diff --git a/examples/phi2/python/phi2.py b/examples/phi2/python/phi2.py deleted file mode 100644 index 3415efb1b..000000000 --- a/examples/phi2/python/phi2.py +++ /dev/null @@ -1,32 +0,0 @@ -import time -import onnxruntime_genai as og - -print(f"Loading model... ") - -# The first argument is the name of the folder containing the model files -model=og.Model(f'example-models/phi2-int4-cpu', og.DeviceType.CPU) -print("Model loaded") - -tokenizer = og.Tokenizer(model) - -prompt = '''def print_prime(n): - """ - Print all primes between 1 and n - """''' - -tokens = tokenizer.encode(prompt) - -params=og.GeneratorParams(model) -params.set_search_options({"max_length":200}) -params.input_ids = tokens - -start_time=time.time() -output_tokens=model.generate(params)[0] -run_time=time.time()-start_time - -print(f"Tokens: {len(output_tokens)} Time: {run_time:.2f} Tokens per second: {len(output_tokens)/run_time:.2f}") - -text = tokenizer.decode(output_tokens) - -print("Output:") -print(text) \ No newline at end of file diff --git a/examples/phi2/python/README.md b/examples/python/README.md similarity index 54% rename from examples/phi2/python/README.md rename to examples/python/README.md index ee2e3c71c..9c7a2cc25 100644 --- a/examples/phi2/python/README.md +++ b/examples/python/README.md @@ -1,9 +1,7 @@ -# Phi-2 +# Gen-AI Python Examples ## Install the onnxruntime-genai library -* (Temporary) Build the library according to [the build instructions](../README.md#build-from-source) - * Install the python package ```bash @@ -20,19 +18,28 @@ pip install numpy pip install transformers pip install torch pip install onnx -pip install onnxruntime +pip install onnxruntime-gpu ``` +Choose a model. Examples of supported ones are: +- Phi-2 +- Mistral +- Gemma 2B IT +- LLama 7B + Run the model builder script to export, optimize, and quantize the model. More details can be found [here](../../src/python/py/models/README.md) ```bash -cd examples/phi2/python +cd examples/python python -m onnxruntime_genai.models.builder -m microsoft/phi-2 -e cpu -p int4 -o ./example-models/phi2-int4-cpu ``` -## Run the phi-2 model +## Run the example model script +See accompanying chat-e2e-example.sh and generate-e2e-example.sh scripts for end-to-end examples of workflow. + +To run the python examples... ```bash -python phi2.py +python model-generate.py -m {path to model folder} -ep {cpu or cuda} -i {string prompt} +python model-chat.py -m {path to model folder} -ep {cpu or cuda} ``` - diff --git a/examples/python/chat-e2e-example.sh b/examples/python/chat-e2e-example.sh new file mode 100755 index 000000000..3245b246f --- /dev/null +++ b/examples/python/chat-e2e-example.sh @@ -0,0 +1,3 @@ +# Description: Example of chatbot end-to-end usage, including model building and running. +python3 -m onnxruntime_genai.models.builder -m microsoft/phi-2 -o genai_models/phi2-int4-cpu -p int4 -e cpu -c hf_cache +python3 model-chat.py -m genai_models/phi2-int4-cpu -ep cpu -p 0.0 -k 1 \ No newline at end of file diff --git a/examples/python/generate-e2e-example.sh b/examples/python/generate-e2e-example.sh new file mode 100755 index 000000000..a6c247954 --- /dev/null +++ b/examples/python/generate-e2e-example.sh @@ -0,0 +1,8 @@ +# Description: Example of generate end-to-end usage, including model building and running. +pip install numpy +pip install transformers +pip install torch +pip install onnx +pip install onnxruntime-gpu +python3 -m onnxruntime_genai.models.builder -m microsoft/phi-2 -o genai_models/phi2-int4-cpu -p int4 -e cpu -c hf_cache +python3 model-generate.py -m genai_models/phi2-int4-cpu -pr "my favorite movie is" "write a function that always returns True" "I am very happy" -ep cpu -p 0.0 -k 1 -v \ No newline at end of file diff --git a/examples/python/model-chat.py b/examples/python/model-chat.py new file mode 100644 index 000000000..e31403065 --- /dev/null +++ b/examples/python/model-chat.py @@ -0,0 +1,42 @@ +import onnxruntime_genai as og +import argparse + +def main(args): + if args.verbose: print("Loading model...") + model = og.Model(f'{args.model}') + if args.verbose: print("Model loaded") + tokenizer = og.Tokenizer(model) + tokenizer_stream = tokenizer.create_stream() + if args.verbose: print("Tokenizer created") + if args.verbose: print() + + # Keep asking for input prompts in an loop + while True: + text = input("Input: ") + input_tokens = tokenizer.encode(text) + + params = og.GeneratorParams(model) + params.set_search_options({"max_length": args.max_length, "top_p": args.top_p, "top_k": args.top_k, "temperature": args.temperature, "repetition_penalty": args.repetition_penalty}) + params.input_ids = input_tokens + generator = og.Generator(model, params) + if args.verbose: print("Generator created") + + if args.verbose: print("Running generation loop ...") + print(f'\n{text}', end='') + while not generator.is_done(): + generator.compute_logits() + generator.generate_next_token_top_k_top_p(args.top_k, args.top_p, args.temperature) + print(tokenizer_stream.decode(generator.get_next_tokens()[0]), end='', flush=True) + print() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="End-to-end chat-bot example for gen-ai") + parser.add_argument('-m', '--model', type=str, required=True, help='Onnx model folder path (must contain config.json and model.onnx)') + parser.add_argument('-l', '--max_length', type=int, default=512, help='Max number of tokens to generate after prompt') + parser.add_argument('-p', '--top_p', type=float, default=0.9, help='Top p probability to sample with') + parser.add_argument('-k', '--top_k', type=int, default=50, help='Top k tokens to sample from') + parser.add_argument('-t', '--temperature', type=float, default=1.0, help='Temperature to sample with') + parser.add_argument('-r', '--repetition_penalty', type=float, default=1.0, help='Repetition penalty to sample with') + parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose output') + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/examples/python/model-generate.py b/examples/python/model-generate.py new file mode 100644 index 000000000..d83c35e85 --- /dev/null +++ b/examples/python/model-generate.py @@ -0,0 +1,52 @@ +import onnxruntime_genai as og +import argparse +import time + +def main(args): + if args.verbose: print("Loading model...") + model = og.Model(f'{args.model}') + if args.verbose: print("Model loaded") + tokenizer = og.Tokenizer(model) + if args.verbose: print("Tokenizer created") + + if args.prompts is not None: + prompts = args.prompts + else: + prompts = ["I like walking my cute dog", + "What is the best restaurant in town?", + "Hello, how are you today?"] + input_tokens = tokenizer.encode_batch(prompts) + if args.verbose: print("Prompt(s) encoded") + + params = og.GeneratorParams(model) + params.set_search_options({"max_length": args.max_length, "top_p": args.top_p, "top_k": args.top_k, "temperature": args.temperature, "repetition_penalty": args.repetition_penalty}) + params.input_ids = input_tokens + if args.verbose: print("GeneratorParams created") + + if args.verbose: print("Generating tokens ...\n") + start_time = time.time() + output_tokens = model.generate(params) + run_time = time.time() - start_time + + for i in range(len(prompts)): + print(f'Prompt #{i}: {prompts[i]}') + print() + print(tokenizer.decode(output_tokens[i])) + print() + + print() + print(f"Tokens: {len(output_tokens[0])} Time: {run_time:.2f} Tokens per second: {len(output_tokens[0])/run_time:.2f}") + print() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="End-to-end token generation loop example for gen-ai") + parser.add_argument('-m', '--model', type=str, required=True, help='Onnx model folder path (must contain config.json and model.onnx)') + parser.add_argument('-pr', '--prompts', nargs='*', required=False, help='Input prompts to generate tokens from') + parser.add_argument('-l', '--max_length', type=int, default=512, help='Max number of tokens to generate after prompt') + parser.add_argument('-p', '--top_p', type=float, default=0.9, help='Top p probability to sample with') + parser.add_argument('-k', '--top_k', type=int, default=50, help='Top k tokens to sample from') + parser.add_argument('-t', '--temperature', type=float, default=1.0, help='Temperature to sample with') + parser.add_argument('-r', '--repetition_penalty', type=float, default=1.0, help='Repetition penalty to sample with') + parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose output') + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec b/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec index d0bb1c7f8..705c7b79d 100644 --- a/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec +++ b/nuget/Microsoft.ML.OnnxRuntimeGenAI.nuspec @@ -3,7 +3,7 @@ - $id$ + Microsoft.ML.OnnxRuntimeGenAI$genai_nuget_ext$ $version$ Microsoft Microsoft @@ -17,12 +17,15 @@ + + + @@ -40,12 +43,12 @@ - - + + - - + + \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index 2004c78a1..74045a524 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -16,6 +16,87 @@ ONNXTensorElementDataType TranslateTensorType(std::string_view value) { throw std::runtime_error("Invalid tensor type: " + std::string(value)); } +struct ProviderOptions_Element : JSON::Element { + explicit ProviderOptions_Element(Config::ProviderOptions& v) : v_{v} {} + + void OnString(std::string_view name, std::string_view value) override { + v_.options.emplace_back(name, value); + } + + private: + Config::ProviderOptions& v_; +}; + +struct ProviderOptionsObject_Element : JSON::Element { + explicit ProviderOptionsObject_Element(std::vector& v) : v_{v} {} + + JSON::Element& OnObject(std::string_view name) override { + if (options_element_) + throw std::runtime_error("Each object in the provider_options array can only have one member (named value)"); + auto& options = v_.emplace_back(); + options.name = name; + options_element_ = std::make_unique(options); + return *options_element_; + } + + private: + std::vector& v_; + std::unique_ptr options_element_; +}; + +struct ProviderOptionsArray_Element : JSON::Element { + explicit ProviderOptionsArray_Element(std::vector& v) : v_{v} {} + + JSON::Element& OnObject(std::string_view name) override { return object_; } + + private: + std::vector& v_; + ProviderOptionsObject_Element object_{v_}; +}; + +struct SessionOptions_Element : JSON::Element { + explicit SessionOptions_Element(Config::SessionOptions& v) : v_{v} {} + + void OnString(std::string_view name, std::string_view value) override { + if (name == "log_id") + v_.log_id = value; + else if (name == "enable_profiling") + v_.enable_profiling = value; + else + throw JSON::unknown_value_error{}; + } + + void OnNumber(std::string_view name, double value) override { + if (name == "intra_op_num_threads") + v_.intra_op_num_threads = static_cast(value); + else if (name == "inter_op_num_threads") + v_.inter_op_num_threads = static_cast(value); + else if (name == "log_severity_level") + v_.log_severity_level = static_cast(value); + else + throw JSON::unknown_value_error{}; + } + + void OnBool(std::string_view name, bool value) override { + if (name == "enable_cpu_mem_arena") + v_.enable_cpu_mem_arena = value; + else if (name == "enable_mem_pattern") + v_.enable_mem_pattern = value; + else + throw JSON::unknown_value_error{}; + } + + JSON::Element& OnArray(std::string_view name) override { + if (name == "provider_options") + return provider_options_; + throw JSON::unknown_value_error{}; + } + + private: + Config::SessionOptions& v_; + ProviderOptionsArray_Element provider_options_{v_.provider_options}; +}; + struct EncoderDecoderInit_Element : JSON::Element { explicit EncoderDecoderInit_Element(Config::Model::EncoderDecoderInit& v) : v_{v} {} @@ -108,6 +189,9 @@ struct Decoder_Element : JSON::Element { } Element& OnObject(std::string_view name) override { + if (name == "session_options") { + return session_options_; + } if (name == "inputs") { return inputs_; } @@ -119,6 +203,7 @@ struct Decoder_Element : JSON::Element { private: Config::Model::Decoder& v_; + SessionOptions_Element session_options_{v_.session_options}; Inputs_Element inputs_{v_.inputs}; Outputs_Element outputs_{v_.outputs}; }; diff --git a/src/config.h b/src/config.h index 807eb7d25..6cc634658 100644 --- a/src/config.h +++ b/src/config.h @@ -8,6 +8,24 @@ struct Config { std::filesystem::path config_path; // Path of the config directory + using ProviderOption = std::pair; + struct ProviderOptions { + std::string name; + std::vector options; + }; + + struct SessionOptions { + std::optional intra_op_num_threads; + std::optional inter_op_num_threads; + std::optional enable_cpu_mem_arena; + std::optional enable_mem_pattern; + std::optional log_id; + std::optional log_severity_level; + std::optional enable_profiling; + + std::vector provider_options; + }; + struct Model { std::string type; @@ -26,6 +44,7 @@ struct Config { struct Decoder { std::string filename; + SessionOptions session_options; int hidden_size{}; // Not currently used, potentially useful for embeddings in the future int num_attention_heads{}; // Not currently used, potentially useful if num_key_value_heads isn't set diff --git a/src/csharp/GeneratorParams.cs b/src/csharp/GeneratorParams.cs index 127555434..5aee3be3e 100644 --- a/src/csharp/GeneratorParams.cs +++ b/src/csharp/GeneratorParams.cs @@ -22,12 +22,12 @@ public GeneratorParams(Model model) public void SetSearchOption(string searchOption, double value) { - Result.VerifySuccess(NativeMethods.OgaGeneratorParamsSetSearchNumber(_generatorParamsHandle, Utils.ToUtf8(searchOption), value)); + Result.VerifySuccess(NativeMethods.OgaGeneratorParamsSetSearchNumber(_generatorParamsHandle, StringUtils.ToUtf8(searchOption), value)); } public void SetSearchOption(string searchOption, bool value) { - Result.VerifySuccess(NativeMethods.OgaGeneratorParamsSetSearchBool(_generatorParamsHandle, Utils.ToUtf8(searchOption), value)); + Result.VerifySuccess(NativeMethods.OgaGeneratorParamsSetSearchBool(_generatorParamsHandle, StringUtils.ToUtf8(searchOption), value)); } public void SetInputIDs(ReadOnlySpan inputIDs, ulong sequenceLength, ulong batchSize) diff --git a/src/csharp/Model.cs b/src/csharp/Model.cs index 23438e912..cf4b7d2a8 100644 --- a/src/csharp/Model.cs +++ b/src/csharp/Model.cs @@ -6,20 +6,14 @@ namespace Microsoft.ML.OnnxRuntimeGenAI { - public enum DeviceType : long - { - CPU = 0, - CUDA = 1 - } - public class Model : IDisposable { private IntPtr _modelHandle; private bool _disposed = false; - public Model(string modelPath, DeviceType deviceType) + public Model(string modelPath) { - Result.VerifySuccess(NativeMethods.OgaCreateModel(Utils.ToUtf8(modelPath), deviceType, out _modelHandle)); + Result.VerifySuccess(NativeMethods.OgaCreateModel(StringUtils.ToUtf8(modelPath), out _modelHandle)); } internal IntPtr Handle { get { return _modelHandle; } } diff --git a/src/csharp/NativeMethods.cs b/src/csharp/NativeMethods.cs index 1d06dc5a3..4b41102d7 100644 --- a/src/csharp/NativeMethods.cs +++ b/src/csharp/NativeMethods.cs @@ -25,7 +25,6 @@ internal class NativeLib [DllImport(NativeLib.DllName, CallingConvention = CallingConvention.Winapi)] public static extern IntPtr /* OgaResult* */ OgaCreateModel(byte[] /* const char* */ configPath, - DeviceType /* OgaDeviceType */ deviceType, out IntPtr /* OgaModel** */ model); [DllImport(NativeLib.DllName, CallingConvention = CallingConvention.Winapi)] @@ -161,5 +160,11 @@ public static extern UIntPtr OgaSequencesGetSequenceCount(IntPtr /* const OgaSeq public static extern IntPtr /* OgaResult* */ OgaTokenizerStreamDecode(IntPtr /* const OgaTokenizerStream* */ tokenizerStream, int /* int32_t */ token, out IntPtr /* const char** */ outStr); + + [DllImport(NativeLib.DllName, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr /* OgaResult* */ OgaSetCurrentGpuDeviceId(int /* int32_t */ device_id); + + [DllImport(NativeLib.DllName, CallingConvention = CallingConvention.Winapi)] + public static extern IntPtr /* OgaResult* */ OgaGetCurrentGpuDeviceId(out IntPtr /* int32_t */ device_id); } } diff --git a/src/csharp/Result.cs b/src/csharp/Result.cs index 965237c60..a6185b907 100644 --- a/src/csharp/Result.cs +++ b/src/csharp/Result.cs @@ -12,7 +12,7 @@ class Result private static string GetErrorMessage(IntPtr nativeResult) { - return Utils.FromUtf8(NativeMethods.OgaResultGetError(nativeResult)); + return StringUtils.FromUtf8(NativeMethods.OgaResultGetError(nativeResult)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/csharp/Tokenizer.cs b/src/csharp/Tokenizer.cs index ead737c80..24fd85ceb 100644 --- a/src/csharp/Tokenizer.cs +++ b/src/csharp/Tokenizer.cs @@ -24,7 +24,7 @@ public Sequences EncodeBatch(string[] strings) { foreach (string str in strings) { - Result.VerifySuccess(NativeMethods.OgaTokenizerEncode(_tokenizerHandle, Utils.ToUtf8(str), nativeSequences)); + Result.VerifySuccess(NativeMethods.OgaTokenizerEncode(_tokenizerHandle, StringUtils.ToUtf8(str), nativeSequences)); } return new Sequences(nativeSequences); @@ -52,7 +52,7 @@ public Sequences Encode(string str) Result.VerifySuccess(NativeMethods.OgaCreateSequences(out IntPtr nativeSequences)); try { - Result.VerifySuccess(NativeMethods.OgaTokenizerEncode(_tokenizerHandle, Utils.ToUtf8(str), nativeSequences)); + Result.VerifySuccess(NativeMethods.OgaTokenizerEncode(_tokenizerHandle, StringUtils.ToUtf8(str), nativeSequences)); return new Sequences(nativeSequences); } catch @@ -74,7 +74,7 @@ public string Decode(ReadOnlySpan sequence) } try { - return Utils.FromUtf8(outStr); + return StringUtils.FromUtf8(outStr); } finally { diff --git a/src/csharp/TokenizerStream.cs b/src/csharp/TokenizerStream.cs index cb42b967e..51cac5a28 100644 --- a/src/csharp/TokenizerStream.cs +++ b/src/csharp/TokenizerStream.cs @@ -22,7 +22,7 @@ public string Decode(int token) { IntPtr decodedStr = IntPtr.Zero; Result.VerifySuccess(NativeMethods.OgaTokenizerStreamDecode(_tokenizerStreamHandle, token, out decodedStr)); - return Utils.FromUtf8(decodedStr); + return StringUtils.FromUtf8(decodedStr); } ~TokenizerStream() diff --git a/src/csharp/Utils.cs b/src/csharp/Utils.cs index c752dd61e..be1005485 100644 --- a/src/csharp/Utils.cs +++ b/src/csharp/Utils.cs @@ -7,7 +7,21 @@ namespace Microsoft.ML.OnnxRuntimeGenAI { - internal class Utils + public class Utils + { + public static void SetCurrentGpuDeviceId(int device_id) + { + Result.VerifySuccess(NativeMethods.OgaSetCurrentGpuDeviceId(device_id)); + } + public static int GetCurrentGpuDeviceId() + { + IntPtr device_id = IntPtr.Zero; + Result.VerifySuccess(NativeMethods.OgaGetCurrentGpuDeviceId(out device_id)); + return (int)device_id.ToInt64(); + } + } + + internal class StringUtils { internal static byte[] EmptyByteArray = new byte[] { 0 }; diff --git a/src/generators.cpp b/src/generators.cpp index 6763a8ed6..db27628da 100644 --- a/src/generators.cpp +++ b/src/generators.cpp @@ -47,24 +47,6 @@ GeneratorParams::GeneratorParams(const Model& model) cuda_stream{model.cuda_stream_} { } -ProviderOptions GetDefaultProviderOptions([[maybe_unused]] DeviceType device_type) { - ProviderOptions options; - if (device_type == DeviceType::CUDA) { -#if USE_CUDA - cudaStream_t cuda_stream; - cudaStreamCreate(&cuda_stream); - - auto& cuda_options = options.emplace(); - cuda_options.has_user_compute_stream = true; - cuda_options.user_compute_stream = cuda_stream; -#else - throw std::runtime_error("Trying to use cuda with the non cuda version of onnxruntime-genai"); -#endif - } - - return options; -} - std::unique_ptr CreateGenerator(const Model& model, const GeneratorParams& params) { return std::make_unique(model, params); } diff --git a/src/generators.h b/src/generators.h index 8f3d066ca..af98aea44 100644 --- a/src/generators.h +++ b/src/generators.h @@ -10,6 +10,7 @@ #include "span.h" #include #include +#include #include #include #include @@ -21,6 +22,9 @@ #if USE_CUDA #include #include "cuda_common.h" +#else +// If we don't include cuda_runtime.h, we define this to avoid lots of extra #ifdefs +using cudaStream_t = void*; #endif #include "smartptrs.h" @@ -35,28 +39,11 @@ struct Search; // OgaSequences are a vector of int32 vectors using TokenSequences = std::vector>; -// If we don't include cuda_runtime.h, we define this to avoid lots of extra #ifdefs -#ifndef USE_CUDA -using cudaStream_t = void*; -#endif - enum struct DeviceType { CPU, CUDA, }; -struct OrtCPUProviderOptions {}; // Stub so that ProviderOptions isn't empty without cuda - -using ProviderOptions = std::variant< - OrtCPUProviderOptions -#if USE_CUDA - , - OrtCUDAProviderOptions -#endif - >; - -ProviderOptions GetDefaultProviderOptions(DeviceType device_type); - struct GeneratorParams { GeneratorParams() = default; // This constructor is only used if doing a custom model handler vs built-in GeneratorParams(const Model& model); @@ -125,7 +112,7 @@ struct Generator { bool computed_logits_{}; // Set to true in ComputeLogits() and false after appending a token to ensure a 1 to 1 call ratio }; -std::unique_ptr CreateModel(OrtEnv& ort_env, const char* config_path, const ProviderOptions* provider_options = nullptr); +std::unique_ptr CreateModel(OrtEnv& ort_env, const char* config_path); std::unique_ptr CreateGenerator(const Model& model, const GeneratorParams& params); std::vector> Generate(const Model& model, const GeneratorParams& params); // Uses CreateGenerator and a simple loop to return the entire sequence diff --git a/src/models/decoder_only.cpp b/src/models/decoder_only.cpp index 3e7abad4a..2bab50aaa 100644 --- a/src/models/decoder_only.cpp +++ b/src/models/decoder_only.cpp @@ -3,8 +3,8 @@ namespace Generators { -DecoderOnly_Model::DecoderOnly_Model(std::unique_ptr config, OrtEnv& ort_env, const ProviderOptions* provider_options) - : Model{std::move(config), provider_options} { +DecoderOnly_Model::DecoderOnly_Model(std::unique_ptr config, OrtEnv& ort_env) + : Model{std::move(config)} { session_decoder_ = OrtSession::Create(ort_env, (config_->config_path / config_->model.decoder.filename).c_str(), session_options_.get()); InitDeviceAllocator(*session_decoder_); diff --git a/src/models/decoder_only.h b/src/models/decoder_only.h index 1e27cdf30..5c997ec65 100644 --- a/src/models/decoder_only.h +++ b/src/models/decoder_only.h @@ -8,7 +8,7 @@ namespace Generators { struct DecoderOnly_Model : Model { - DecoderOnly_Model(std::unique_ptr config, OrtEnv& ort_env, const ProviderOptions* provider_options); + DecoderOnly_Model(std::unique_ptr config, OrtEnv& ort_env); std::unique_ptr CreateState(RoamingArray sequence_lengths, const GeneratorParams& params) const override; diff --git a/src/models/gpt.cpp b/src/models/gpt.cpp index 46fb4ba67..18d802116 100644 --- a/src/models/gpt.cpp +++ b/src/models/gpt.cpp @@ -3,8 +3,8 @@ namespace Generators { -Gpt_Model::Gpt_Model(std::unique_ptr config, OrtEnv& ort_env, const ProviderOptions* provider_options) - : Model{std::move(config), provider_options} { +Gpt_Model::Gpt_Model(std::unique_ptr config, OrtEnv& ort_env) + : Model{std::move(config)} { session_decoder_ = OrtSession::Create(ort_env, (config_->config_path / config_->model.decoder.filename).c_str(), session_options_.get()); InitDeviceAllocator(*session_decoder_); } diff --git a/src/models/gpt.h b/src/models/gpt.h index 3663f3e56..5d9eac22b 100644 --- a/src/models/gpt.h +++ b/src/models/gpt.h @@ -8,7 +8,7 @@ namespace Generators { struct Gpt_Model : Model { - Gpt_Model(std::unique_ptr config, OrtEnv& ort_env, const ProviderOptions* provider_options); + Gpt_Model(std::unique_ptr config, OrtEnv& ort_env); std::unique_ptr CreateState(RoamingArray sequence_lengths, const GeneratorParams& params) const override; diff --git a/src/models/model.cpp b/src/models/model.cpp index 50d491af6..993b66248 100644 --- a/src/models/model.cpp +++ b/src/models/model.cpp @@ -202,23 +202,8 @@ ONNXTensorElementDataType SessionInfo::GetOutputDataType(const std::string& name return result->second; } -Model::Model(std::unique_ptr config, const ProviderOptions* provider_options) : config_{std::move(config)} { - session_options_ = OrtSessionOptions::Create(); - - constexpr int min_thread_nums = 1; - constexpr int max_thread_nums = 16; - int num_of_cores = std::max(min_thread_nums, static_cast(std::thread::hardware_concurrency() / 2)); - session_options_->SetIntraOpNumThreads(std::min(num_of_cores, max_thread_nums)); - - if (provider_options != nullptr) { -#if USE_CUDA - if (auto* options = std::get_if(provider_options)) { - cuda_stream_ = reinterpret_cast(options->user_compute_stream); - session_options_->AppendExecutionProvider_CUDA(*options); - device_type_ = DeviceType::CUDA; - } -#endif - } +Model::Model(std::unique_ptr config) : config_{std::move(config)} { + CreateSessionOptions(); } Model::~Model() = default; @@ -233,19 +218,98 @@ void Model::InitDeviceAllocator([[maybe_unused]] OrtSession& session) { session_info_ = std::make_unique(session); } +void Model::CreateSessionOptions() { + session_options_ = OrtSessionOptions::Create(); + auto& ort_options = *session_options_; + auto& options = config_->model.decoder.session_options; + + // Default to a limit of 16 threads to optimize performance + constexpr int min_thread_nums = 1; + constexpr int max_thread_nums = 16; + int num_of_cores = std::max(min_thread_nums, static_cast(std::thread::hardware_concurrency() / 2)); + ort_options.SetIntraOpNumThreads(std::min(num_of_cores, max_thread_nums)); + + if (options.intra_op_num_threads.has_value()) { + ort_options.SetIntraOpNumThreads(options.intra_op_num_threads.value()); + } + + if (options.inter_op_num_threads.has_value()) { + ort_options.SetInterOpNumThreads(options.inter_op_num_threads.value()); + } + + if (options.enable_cpu_mem_arena.has_value()) { + if (options.enable_cpu_mem_arena.value()) + ort_options.EnableCpuMemArena(); + else + ort_options.DisableCpuMemArena(); + } + + if (options.enable_mem_pattern.has_value()) { + if (options.enable_cpu_mem_arena.value()) + ort_options.EnableMemPattern(); + else + ort_options.DisableMemPattern(); + } + + if (options.log_id.has_value()) { + ort_options.SetLogId(options.log_id.value().c_str()); + } + + if (options.log_severity_level.has_value()) { + ort_options.SetLogSeverityLevel(options.log_severity_level.value()); + } + + if (options.enable_profiling.has_value()) { + std::filesystem::path profile_file_prefix{options.enable_profiling.value()}; + ort_options.EnableProfiling(profile_file_prefix.c_str()); + } + + for (auto& provider_options : options.provider_options) { + if (provider_options.name == "cuda") { + auto ort_provider_options = OrtCUDAProviderOptionsV2::Create(); + std::vector keys, values; + for (auto& option : provider_options.options) { + keys.emplace_back(option.first.c_str()); + values.emplace_back(option.second.c_str()); + } + ort_provider_options->Update(keys.data(), values.data(), keys.size()); + + // Create and set our cudaStream_t + cuda_stream_.Create(); + ort_provider_options->UpdateValue("user_compute_stream", cuda_stream_.get()); + + ort_options.AppendExecutionProvider_CUDA_V2(*ort_provider_options); + device_type_ = DeviceType::CUDA; // Scoring will use CUDA + } else if (provider_options.name == "rocm") { + OrtROCMProviderOptions ort_provider_options; + + std::vector keys, values; + for (auto& option : provider_options.options) { + keys.emplace_back(option.first.c_str()); + values.emplace_back(option.second.c_str()); + } + + Ort::ThrowOnError(Ort::api->UpdateROCMProviderOptions(&ort_provider_options, keys.data(), values.data(), keys.size())); + ort_options.AppendExecutionProvider_ROCM(ort_provider_options); + device_type_ = DeviceType::CPU; // Scoring uses CPU, even though the model uses ROCM + } else + throw std::runtime_error("Unknown provider type: " + provider_options.name); + } +} + std::unique_ptr Model::CreateTokenizer() const { return std::make_unique(*config_); } -std::unique_ptr CreateModel(OrtEnv& ort_env, const char* config_path, const ProviderOptions* provider_options) { +std::unique_ptr CreateModel(OrtEnv& ort_env, const char* config_path) { auto config = std::make_unique(config_path); if (config->model.type == "gpt2") - return std::make_unique(std::move(config), ort_env, provider_options); + return std::make_unique(std::move(config), ort_env); if (config->model.type == "llama" || config->model.type == "gemma" || config->model.type == "mistral" || config->model.type == "phi") - return std::make_unique(std::move(config), ort_env, provider_options); + return std::make_unique(std::move(config), ort_env); if (config->model.type == "whisper") - return std::make_unique(std::move(config), ort_env, provider_options); + return std::make_unique(std::move(config), ort_env); throw std::runtime_error("Unsupported model_type in config.json: " + config->model.type); } diff --git a/src/models/model.h b/src/models/model.h index fc02eb649..3f1d4ceca 100644 --- a/src/models/model.h +++ b/src/models/model.h @@ -95,7 +95,7 @@ struct SessionInfo { }; struct Model { - Model(std::unique_ptr config, const ProviderOptions* provider_options); + Model(std::unique_ptr config); virtual ~Model(); std::unique_ptr CreateTokenizer() const; @@ -106,7 +106,7 @@ struct Model { std::unique_ptr config_; std::unique_ptr session_options_; - cudaStream_t cuda_stream_{}; + cuda_stream_holder cuda_stream_; DeviceType device_type_{DeviceType::CPU}; Ort::Allocator& allocator_cpu_{Ort::Allocator::GetWithDefaultOptions()}; Ort::Allocator* allocator_device_{}; // Can be CUDA or CPU based on the DeviceType in the model @@ -115,6 +115,7 @@ struct Model { protected: void InitDeviceAllocator(OrtSession& session); + void CreateSessionOptions(); }; } // namespace Generators diff --git a/src/models/onnxruntime_api.h b/src/models/onnxruntime_api.h index 541be9493..243241a87 100644 --- a/src/models/onnxruntime_api.h +++ b/src/models/onnxruntime_api.h @@ -101,6 +101,9 @@ struct Exception : std::exception { /// This is a C++ wrapper for OrtApi::GetAvailableProviders() and returns a vector of strings representing the available execution providers. std::vector GetAvailableProviders(); +inline void SetCurrentGpuDeviceId(int device_id); +inline int GetCurrentGpuDeviceId(); + /** \brief IEEE 754 half-precision floating point data type * \details It is necessary for type dispatching to make use of C++ API * The type is implicitly convertible to/from uint16_t. @@ -332,6 +335,16 @@ struct OrtRunOptions { Ort::Abstract make_abstract; }; +struct OrtCUDAProviderOptionsV2 { + static std::unique_ptr Create(); + + void Update(const char* const* keys, const char* const* values, size_t count); + void UpdateValue(const char* key, void* value); + + static void operator delete(void* p) { Ort::api->ReleaseCUDAProviderOptions(reinterpret_cast(p)); } + Ort::Abstract make_abstract; +}; + /** \brief Options object used when creating a new Session object * * Wraps ::OrtSessionOptions object and methods diff --git a/src/models/onnxruntime_inline.h b/src/models/onnxruntime_inline.h index 120d96c04..1367ac643 100644 --- a/src/models/onnxruntime_inline.h +++ b/src/models/onnxruntime_inline.h @@ -150,6 +150,16 @@ inline std::unique_ptr Allocator::Create(const OrtSession& sess, cons return std::unique_ptr{static_cast(p)}; } +inline void SetCurrentGpuDeviceId(int device_id) { + api->SetCurrentGpuDeviceId(device_id); +} + +inline int GetCurrentGpuDeviceId() { + int id; + ThrowOnError(api->GetCurrentGpuDeviceId(&id)); + return id; +} + } // namespace Ort inline std::unique_ptr OrtStatus::Create(OrtErrorCode code, const std::string& what) { @@ -443,6 +453,20 @@ inline OrtRunOptions& OrtRunOptions::UnsetTerminate() { return *this; } +inline std::unique_ptr OrtCUDAProviderOptionsV2::Create() { + OrtCUDAProviderOptionsV2* p; + Ort::ThrowOnError(Ort::api->CreateCUDAProviderOptions(&p)); + return std::unique_ptr{p}; +} + +inline void OrtCUDAProviderOptionsV2::Update(const char* const* keys, const char* const* values, size_t count) { + Ort::ThrowOnError(Ort::api->UpdateCUDAProviderOptions(this, keys, values, count)); +} + +inline void OrtCUDAProviderOptionsV2::UpdateValue(const char* key, void* value) { + Ort::ThrowOnError(Ort::api->UpdateCUDAProviderOptionsWithValue(this, key, value)); +} + inline std::unique_ptr OrtSessionOptions::Create() { OrtSessionOptions* p; Ort::ThrowOnError(Ort::api->CreateSessionOptions(&p)); diff --git a/src/models/position_ids.cpp b/src/models/position_ids.cpp index 5e00614ad..bfff6e161 100644 --- a/src/models/position_ids.cpp +++ b/src/models/position_ids.cpp @@ -14,16 +14,16 @@ PositionIDs::PositionIDs(const Model& model, State& state, RoamingArray std::array shape{state_.params_.batch_size, state_.params_.sequence_length}; // Only batch_size initially, as we haven't expanded over the beams yet position_ids_ = OrtValue::CreateTensor(model.allocator_cpu_, shape, type_); + position_ids_next_ = OrtValue::CreateTensor(model.allocator_cpu_, std::array{shape[0], 1}, type_); attention_mask_ = OrtValue::CreateTensor(model.allocator_cpu_, shape, type_); - initial_sequence_lengths_.resize(state_.params_.BatchBeamSize()); - if (type_ == Ort::TypeToTensorType::type) InitializeTensors(shape, sequence_lengths_unk); else InitializeTensors(shape, sequence_lengths_unk); position_ids_ = model_.ExpandInputs(position_ids_, state_.params_.search.num_beams); + position_ids_next_ = model_.ExpandInputs(position_ids_next_, state_.params_.search.num_beams); attention_mask_ = model_.ExpandInputs(attention_mask_, state_.params_.search.num_beams); shape[0] *= state_.params_.search.num_beams; position_ids_shape_ = shape; @@ -42,31 +42,10 @@ void PositionIDs::Add() { void PositionIDs::Update(int current_length) { // Reallocate position_ids for the 2nd and onward shape - if (initial_sequence_lengths_.size()) { + if (position_ids_next_) { + position_ids_ = std::move(position_ids_next_); position_ids_shape_[1] = 1; - position_ids_ = OrtValue::CreateTensor(*model_.allocator_device_, position_ids_shape_, type_); state_.inputs_[input_index_] = position_ids_.get(); - - // Copy the initial values over to the device specific tensor - switch (model_.device_type_) { - case DeviceType::CPU: - if (type_ == Ort::TypeToTensorType::type) - std::copy(initial_sequence_lengths_.begin(), initial_sequence_lengths_.end(), position_ids_->GetTensorMutableData()); - else - std::copy(initial_sequence_lengths_.begin(), initial_sequence_lengths_.end(), position_ids_->GetTensorMutableData()); - break; -#if USE_CUDA - case DeviceType::CUDA: - if (type_ == Ort::TypeToTensorType::type) - cudaMemcpyAsync(position_ids_->GetTensorMutableRawData(), initial_sequence_lengths_.data(), sizeof(int32_t) * initial_sequence_lengths_.size(), cudaMemcpyHostToDevice, model_.cuda_stream_); - else - cudaMemcpyAsync(position_ids_->GetTensorMutableRawData(), initial_sequence_lengths_.data(), sizeof(int64_t) * initial_sequence_lengths_.size(), cudaMemcpyHostToDevice, model_.cuda_stream_); - break; -#endif - default: - throw std::runtime_error("PositionIDs::Update - Unsupported device type"); - } - initial_sequence_lengths_.clear(); } else { // Just incrementing existing position IDs switch (model_.device_type_) { case DeviceType::CPU: { @@ -126,6 +105,7 @@ void PositionIDs::InitializeTensors(std::array shape, cpu_spanGetTensorMutableData(); auto* position_data = position_ids_->GetTensorMutableData(); + auto* position_data_next = position_ids_next_->GetTensorMutableData(); const auto* word_id = state_.params_.input_ids.data(); auto* mask = mask_data; auto* position = position_data; @@ -141,9 +121,9 @@ void PositionIDs::InitializeTensors(std::array shape, cpu_span(abs_position); - initial_sequence_lengths_[i * state_.params_.search.num_beams + k] = static_cast(abs_position); } } } diff --git a/src/models/position_ids.h b/src/models/position_ids.h index 186653f15..411b55c1c 100644 --- a/src/models/position_ids.h +++ b/src/models/position_ids.h @@ -27,7 +27,7 @@ struct PositionIDs { std::array attention_mask_shape_{}; // {params.batch_size*params.beam_size, params.sequence_length} std::unique_ptr attention_mask_; - std::vector initial_sequence_lengths_; + std::unique_ptr position_ids_next_; // Replaces position_ids_ after the first Run() call }; } // namespace Generators diff --git a/src/models/whisper.cpp b/src/models/whisper.cpp index 6752acaca..f6f2aaea1 100644 --- a/src/models/whisper.cpp +++ b/src/models/whisper.cpp @@ -3,8 +3,8 @@ namespace Generators { -Whisper_Model::Whisper_Model(std::unique_ptr config, OrtEnv& ort_env, const ProviderOptions* provider_options) - : Model{std::move(config), provider_options} { +Whisper_Model::Whisper_Model(std::unique_ptr config, OrtEnv& ort_env) + : Model{std::move(config)} { session_decoder_ = OrtSession::Create(ort_env, (config_->config_path / config_->model.decoder.filename).c_str(), session_options_.get()); session_encoder_ = OrtSession::Create(ort_env, (config_->config_path / config_->model.encoder_decoder_init.filename).c_str(), session_options_.get()); diff --git a/src/models/whisper.h b/src/models/whisper.h index 0d38cd72d..94104840b 100644 --- a/src/models/whisper.h +++ b/src/models/whisper.h @@ -6,7 +6,7 @@ namespace Generators { struct Whisper_Model : Model { - Whisper_Model(std::unique_ptr config, OrtEnv& ort_env, const ProviderOptions* provider_options); + Whisper_Model(std::unique_ptr config, OrtEnv& ort_env); std::unique_ptr CreateState(RoamingArray sequence_lengths, const GeneratorParams& params) const override; diff --git a/src/ort_genai_c.cpp b/src/ort_genai_c.cpp index 9805bcc5d..25b0edf18 100644 --- a/src/ort_genai_c.cpp +++ b/src/ort_genai_c.cpp @@ -61,10 +61,9 @@ const int32_t* OGA_API_CALL OgaSequencesGetSequenceData(const OgaSequences* p, s return (*reinterpret_cast(p))[sequence].data(); } -OgaResult* OGA_API_CALL OgaCreateModel(const char* config_path, OgaDeviceType device_type, OgaModel** out) { +OgaResult* OGA_API_CALL OgaCreateModel(const char* config_path, OgaModel** out) { OGA_TRY - auto provider_options = Generators::GetDefaultProviderOptions(static_cast(device_type)); - *out = reinterpret_cast(Generators::CreateModel(Generators::GetOrtEnv(), config_path, &provider_options).release()); + *out = reinterpret_cast(Generators::CreateModel(Generators::GetOrtEnv(), config_path).release()); return nullptr; OGA_CATCH } @@ -211,6 +210,20 @@ OgaResult* OGA_API_CALL OgaTokenizerStreamDecode(OgaTokenizerStream* p, int32_t OGA_CATCH } +OGA_EXPORT OgaResult* OGA_API_CALL OgaSetCurrentGpuDeviceId(int device_id) { + OGA_TRY + Ort::SetCurrentGpuDeviceId(device_id); + return nullptr; + OGA_CATCH +} + +OGA_EXPORT OgaResult* OGA_API_CALL OgaGetCurrentGpuDeviceId(int* device_id) { + OGA_TRY + *device_id = Ort::GetCurrentGpuDeviceId(); + return nullptr; + OGA_CATCH +} + void OGA_API_CALL OgaDestroyResult(OgaResult* p) { delete p; } diff --git a/src/ort_genai_c.h b/src/ort_genai_c.h index 1d9de42af..255bfbafb 100644 --- a/src/ort_genai_c.h +++ b/src/ort_genai_c.h @@ -27,11 +27,6 @@ extern "C" { // ONNX Runtime Generative AI C API // This API is not thread safe. -typedef enum OgaDeviceType { - OgaDeviceTypeCPU, - OgaDeviceTypeCUDA, -} OgaDeviceType; - typedef struct OgaResult OgaResult; typedef struct OgaGeneratorParams OgaGeneratorParams; typedef struct OgaGenerator OgaGenerator; @@ -91,7 +86,7 @@ OGA_EXPORT const int32_t* OGA_API_CALL OgaSequencesGetSequenceData(const OgaSequ * \param[out] out The created model. * \return OgaResult containing the error message if the model creation failed. */ -OGA_EXPORT OgaResult* OGA_API_CALL OgaCreateModel(const char* config_path, OgaDeviceType device_type, OgaModel** out); +OGA_EXPORT OgaResult* OGA_API_CALL OgaCreateModel(const char* config_path, OgaModel** out); /* * \brief Destroys the given model. @@ -229,6 +224,9 @@ OGA_EXPORT void OGA_API_CALL OgaDestroyTokenizerStream(OgaTokenizerStream*); */ OGA_EXPORT OgaResult* OGA_API_CALL OgaTokenizerStreamDecode(OgaTokenizerStream*, int32_t token, const char** out); +OGA_EXPORT OgaResult* OGA_API_CALL OgaSetCurrentGpuDeviceId(int device_id); +OGA_EXPORT OgaResult* OGA_API_CALL OgaGetCurrentGpuDeviceId(int* device_id); + #ifdef __cplusplus } #endif diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 1a2a01703..a991a6167 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -24,6 +24,11 @@ if(NOT WIN32) endif() set_target_properties(python PROPERTIES OUTPUT_NAME "onnxruntime_genai") +if(CMAKE_GENERATOR_TOOLSET MATCHES "Visual Studio") + target_link_options(python PRIVATE "/CETCOMPAT") + target_compile_options(python PRIVATE "/sdl" PRIVATE "/Qspectre") +endif() + if(NOT WIN32) target_link_libraries(python PRIVATE stdc++fs) endif() diff --git a/src/python/py/models/DESIGN.md b/src/python/py/models/DESIGN.md new file mode 100644 index 000000000..585450c96 --- /dev/null +++ b/src/python/py/models/DESIGN.md @@ -0,0 +1,165 @@ +# Design of ONNX Runtime GenAI Model Builder + +This document explains how the model builder is designed and how new contributions can be made. By following these guidelines, the model builder will remain lightweight, flexible, and useful after future changes. + +# Contents +- [Design Principles](#design-principles) + - [Simplicity](#simplicity) + - [Efficiency](#efficiency) + - [Modularity](#modularity) + - [Compatibility](#compatibility) +- [Implementation Details](#implementation-details) + - [`Model`](#model) + - [Architecture Classes](#architecture-classes) + - [`Attrs` Dictionaries](#attrs-dictionaries) + - [`Make` Functions](#make-functions) + - [Namespaces](#namespaces) + - [Constants](#constants) +- [Contributing](#contributing) + +## Design Principles +The model builder is designed under four main guiding principles: simplicity, efficiency, modularity, and compatibility. + +### Simplicity + +There should only be a few command-line arguments that are passed to the model builder. Additional arguments that are scenario-specific should not be created as separate command-line arguments. Instead, they should be passed to the `--extra_options` argument and the model builder can parse these custom arguments as key-value pairs. + +There should only be a few dependencies on other packages. Additional package dependencies should not be added unless there is a strong justification. + +These requirements are in place to make the model builder both lightweight and portable. The model builder has no dependencies on the ONNX Runtime GenAI package and it can be used as a standalone tool. + +### Efficiency + +Excluding the time it may take to download the original model, the model builder should produce optimized and quantized ONNX models within a few minutes. The traditional pipeline of converting models to ONNX, optimizing ONNX models, and quantizing ONNX models should be greatly accelerated. + +### Modularity + +Model architectures are defined in classes. By defining model architectures in this way, future models can leverage class inheritance to greatly reduce the amount of time and effort needed to onboard new model architectures. Optimizations and quantizations are also inherited from the base classes so there is no additional work needed to add those opportunities per model. + +### Compatibility + +The models produced by the model builder should directly work in ONNX Runtime GenAI and other solutions that use ONNX Runtime such as Hugging Face's Optimum. There should be no additional model modifications needed. + +## Implementation Details + +### `Model` + +The `Model` base class holds all of the information for making models. It auto-determines optimizations and quantizations that can be applied (e.g. replace MultiHeadAttention with GroupQueryAttention). It also holds all important attributes and the many functions that make the final ONNX model. + +After the final ONNX model is created, additional files are saved in the output folder to run with ONNX Runtime GenAI. These include the GenAI config and the pre-processing/post-processing files (e.g. tokenizer). + +### Architecture Classes + +Classes are the main abstraction within the model builder. Information that is specific to a particular model architecture is stored within the model architecture's class. Class inheritance is used to re-use existing code and reduce the time it takes to support a new model architecture. It also allows for any functions in the `Model` class to be overwritten as needed. Any changes in the `Model` class that add or modify support for optimization or quantization will benefit all models without any additional effort to support per model. + +When adding support for a new model architecture, it is preferred to make changes in the `Model` class when possible. This allows the functions in `Model` to become more generic to multiple model architectures. An example of this is the changes made to support the `GemmaModel` class. If the changes are large or the model architecture is different than the currently supported options, it is easier to override or create new `Make` functions in the new model architecture's class. Examples of this include the changes made to support the `MistralModel` and `PhiModel` classes. + +### `Attrs` Dictionaries + +Attributes specific to operators and their scenario-specific variables are created and defined in the `Model` class under the operator's dictionary of variables. They should not be created and defined within functions. With this approach, information is globally accessible at all times and across all layers. This is particularly useful when creating an ONNX model where components are added or removed within it to support the user's requested scenario. + +For example, a `LayerNorm` node can have two outputs during inference. One output is used as the input for the next nodes and one output is used as the input for the next `LayerNorm`. The next nodes may change depending on the layer and the next `LayerNorm` may happen after several nodes are added into the ONNX model. To handle these scenarios, the `LayerNorm` outputs need to be saved and updated globally. + +New atributes and new scenario-specific variables (e.g. `add_offset` in `self.layernorm_attrs`, `normalize` in `self.embed_attrs`, etc.) should be created and defined in the `Model` class under the operator's dictionary of variables. For every new entry added, please add a comment that describes its purpose. + +### `Make` Functions + +`Make` functions are another main abstraction in the model builder. The base `Model` class holds many of the `Make` functions and their implementations. Each model architecture class inherits the `Make` functions and can override them as needed (e.g. `make_layer` in `PhiModel`). + +Each `Make` function should take only a few basic parameters that are required for all situations when creating an op. If there are parameters that are scenario-specific, you can either initialize them in the `Attrs` dictionaries, create a new `Attrs` dictionary, or pass them as key-value parameters through `**kwargs` if already used to create an op. + +A strong example of this is `make_attention`. There are no specified parameters besides the layer number, the attention module holding all input weights, and the root input to the attention layer because the remaining parameters will differ across attention ops. Similarly, when the attention op is created in `make_attention_op`, only the op's name is a specified parameter. + +By using `**kwargs`, the `Make` function's signature is simplified and easy to read while still retaining the flexibility to pass any parameters needed for attention to it. With this approach, subclasses that inherit the `Model` class can also add or override any parameters as needed (e.g. `make_rotary_embedding` in `PhiModel`). + +If a `Make` function is creating more than one op, please draw a diagram of the subgraph before its implementation. Diagrams help clarify the purpose of the function and visually show what the subgraph looks like afterwards. + +Finally, creating many `Make` functions to abstract the entire model building process allows for fully customizing the process of building new models without having to modify other function signatures and worry about the fallout effects from those changes. + +### Namespaces + +Names are created using the following namespace format. + +``` +"/model/{unique description to reach location in the graph through multiple category names that are split up by '/'}/{op type}" +``` + +For example, + +``` +"/model/layers.0/attn/q_proj/MatMul" +``` + +can be read as + +1. Look inside the model +2. Look at the 0th layer in the model +3. Look at the attention subgraph in the 0th layer in the model +4. Look at the Q projection path in the attention subgraph in the 0th layer in the model +5. Look at the MatMul node in the Q projection path in the attention subgraph in the 0th layer in the model + +Thus, each node in the model has a unique name that describes its location in the model. + +The output names for each node are created using the following namespace format. + +``` +"{node name}/output_{output index}" +``` + +Because each node's name is unique, the output names for all nodes are unique. The output names are based on the node names and the output index to avoid needing to create and keep track of unique output names as well. + +For example, + +``` +"/model/layers.0/attn/o_proj/MatMul/output_0" +``` + +is the 0th output of the MatMul node in the output projection path in the attention subgraph in the 0th layer in the model. Therefore, whether the next node after this MatMul is an `Add` node or a `RotaryEmbedding` node, the next node just has to be aware of the parent node's name. The output index to use can be inferred based on the scenario in the graph. + +This namespace format also allows components to be moved around or swapped in and out as needed. By knowing what the output node is in a component or layer, only its name needs to be provided to other components or layers. For example, a graph structure of `MLP --> layernorm` vs `MLP --> residual add` only has to differ by calling the `Make` function that creates the layernorm vs. the `Make` function that creates the residual add. + +### Constants + +Within the `Model` class, constants in ONNX are created through an automated process. + +The `self.constants` holds the names of all constants that are used in the ONNX model. By keeping track of which constants are already created, the size of the final ONNX model can be reduced by re-using constants in this set. + +In the traditional export process, a unique constant is created each time a node in the ONNX model needs one. The new constants may have the same values and dtypes but their stored names are different. Because of this, the ONNX model will store them both. Many of these stored constants are duplicates that do not need to be in the ONNX model. + +To automate the creation process, the names of the constants are stored in the following namespace format. + +``` +"/model/constants/{onnx dtype}/{shape}/{num}" +``` + +- The `onnx_dtype` is the string representation of the TensorProto enum name used in ONNX to represent the constant's dtype (e.g. `TensorProto.INT64`). It is created using `self.to_str_dtype`. +- The `shape` is either `0D` (0-dimensional constant) or `1D` (1-dimensional constant). +- The `num` is the numerical constant. + +When a node is added to the ONNX model, its inputs are parsed to identify names in this format. If recognized, the input name (which is the name of a constant) is looked up in `self.constants`. If found, then the constant does not need to be added to the ONNX model again. If not found, the input name is parsed to obtain the necessary info to create the ONNX constant. + +## Contributing + +To contribute to the model builder, please ensure the following requirements are met. + +1. Please verify your changes maintain the above information. + +2. For new model architectures added, please verify that ONNX models produced by the model builder are valid. This can be done by loading the ONNX model into an ONNX Runtime inference session. + +Python example: +``` +import onnxruntime as ort + +model_path = "path_to_onnx_model" +ep = "name_of_desired_execution_provider" + +sess = ort.InferenceSession(model_path, providers=[ep]) +``` + +ONNX Runtime GenAI has additional CI tests to verify valid models when pull requests are opened. + +3. Please ensure no attributes and no scenario-specific variables are created within a `Make` function or its function signature in `Model`. They should be defined in the `Attrs` dictionaries (preferably) or by overriding the function signature in the model architecture's class. + +4. For adding new classes of model architectures (e.g. encoder-decoder, diffusion pipelines, multi-modal), please create a separate file that defines the new architecture class and have the base class in the new file inherit the `Model` class. While decoder-only architectures are currently the only ones supported, some of the logic in `Model` may be re-factored and put into a `DecoderModel` class in the future to help with this. + +Please feel free to open an issue if you have any questions! \ No newline at end of file diff --git a/src/python/py/models/README.md b/src/python/py/models/README.md index f39c1d941..0fdd2c818 100644 --- a/src/python/py/models/README.md +++ b/src/python/py/models/README.md @@ -1,6 +1,6 @@ # ONNX Runtime GenAI Model Builder -This folder contains the model builder tool, which greatly accelerates creating optimized and quantized ONNX models that run with ONNX Runtime GenAI. +This folder contains the model builder for quickly creating optimized and quantized ONNX models within a few minutes that run with ONNX Runtime GenAI. # Contents - [Current Support](#current-support) @@ -11,6 +11,7 @@ This folder contains the model builder tool, which greatly accelerates creating - [Customized or Finetuned PyTorch Model](#customized-or-finetuned-pytorch-model) - [GGUF Model](#gguf-model) - [Extra Options](#extra-options) + - [Config Only](#config-only) - [Unit Testing Models](#unit-testing-models) - [Option 1: Use the model builder tool directly](#option-1-use-the-model-builder-tool-directly) - [Option 2: Edit the config.json file](#option-2-edit-the-configjson-file-on-disk-and-then-run-the-model-builder-tool) @@ -23,6 +24,8 @@ The tool currently supports the following model architectures. - Mistral - Phi +It is intended for supporting the latest, popular state-of-the-art models. + ## Usage ### Full Usage @@ -86,6 +89,18 @@ python3 builder.py -m model_name -o path_to_output_folder -p precision -e execut ``` To see all available options through `--extra_options`, please use the `help` commands in the `Full Usage` section above. +### Config Only +This scenario is for when you already have your optimized and/or quantized ONNX model and you need to create the config files to run with ONNX Runtime GenAI. +``` +# From wheel: +python3 -m onnxruntime_genai.models.builder -m model_name -o path_to_output_folder -p precision -e execution_provider -c cache_dir_for_hf_files --extra_options config_only=true + +# From source: +python3 builder.py -m model_name -o path_to_output_folder -p precision -e execution_provider -c cache_dir_for_hf_files --extra_options config_only=true +``` + +Afterwards, please open the `genai_config.json` file in the output folder and modify the fields as needed for your model. You should store your ONNX model in the output folder as well. + ### Unit Testing Models This scenario is where your PyTorch model is already downloaded locally (either in the default Hugging Face cache directory or in a local folder on disk). If it is not already downloaded locally, here is an example of how you can download it. @@ -124,4 +139,8 @@ python3 -m onnxruntime_genai.models.builder -m model_name -o path_to_output_fold # From source: python3 builder.py -m model_name -o path_to_output_folder -p precision -e execution_provider -c cache_dir_where_hf_files_are_saved -``` \ No newline at end of file +``` + +## Design + +Please read the [design document](DESIGN.md) for more details and for how to contribute. \ No newline at end of file diff --git a/src/python/py/models/builder.py b/src/python/py/models/builder.py index b44953e7f..1652d5778 100644 --- a/src/python/py/models/builder.py +++ b/src/python/py/models/builder.py @@ -25,7 +25,7 @@ def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options): self.window_size = config.sliding_window if hasattr(config, "sliding_window") else -1 # default is -1 in GroupQueryAttention kernel self.intermediate_size = config.intermediate_size self.hidden_size = config.hidden_size - self.num_kv_heads = config.num_key_value_heads + self.num_kv_heads = config.num_key_value_heads if hasattr(config, "num_key_value_heads") else config.num_attention_heads self.num_attn_heads = config.num_attention_heads self.head_size = config.head_dim if hasattr(config, "head_dim") else config.hidden_size // config.num_attention_heads self.num_layers = int(extra_options["num_hidden_layers"]) if "num_hidden_layers" in extra_options else config.num_hidden_layers @@ -102,12 +102,13 @@ def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options): # RotaryEmbedding-specific variables partial_rotary_factor = config.partial_rotary_factor if hasattr(config, "partial_rotary_factor") else 1.0 + rope_theta = config.rope_theta if hasattr(config, "rope_theta") else 10000 self.rotemb_attrs = { "create_rotary_embedding_caches": True, # Create cos/sin caches for rotary embeddings "partial_rotary_factor": partial_rotary_factor, # Factor for partial rotary embeddings "num_heads": 0, # For partial rotary embeddings (RotaryEmbedding kernel expects a default value of 0) "rotary_embedding_dim": 0, # For partial rotary embeddings (RotaryEmbedding kernel expects a default value of 0) - "theta": config.rope_theta, # Base value if calculating cos/sin caches from scratch + "theta": rope_theta, # Base value if calculating cos/sin caches from scratch } # Attention-specific variables (MHA, GQA, GQA + Rot.Emb., etc.) @@ -138,6 +139,10 @@ def make_genai_config(self, model_name_or_path, extra_kwargs, out_dir): "bos_token_id": config.bos_token_id, "context_length": self.context_length, "decoder": { + "session_options" : { + "log_id": "onnxruntime-genai", + "provider_options" : [] + }, "filename": self.filename, "head_size": self.head_size, "hidden_size": self.hidden_size, @@ -174,6 +179,10 @@ def make_genai_config(self, model_name_or_path, extra_kwargs, out_dir): }, } + if self.ep == "cuda": + cuda_options = { "cuda" : { } } + genai_config["model"]["decoder"]["session_options"]["provider_options"].append(cuda_options) + print(f"Saving GenAI config in {out_dir}") with open(os.path.join(out_dir,"genai_config.json"), "w") as f: json.dump(genai_config, f, indent=4) @@ -1392,23 +1401,26 @@ def create_model(model_name, input_path, output_dir, precision, execution_provid # Set input/output precision of ONNX model io_dtype = TensorProto.FLOAT if precision in {"int8", "fp32"} or (precision == "int4" and execution_provider == "cpu") else TensorProto.FLOAT16 - # List architecture options in alphabetical order - if config.architectures[0] == "GemmaForCausalLM": - onnx_model = GemmaModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) - elif config.architectures[0] == "LlamaForCausalLM": - onnx_model = LlamaModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) - elif config.architectures[0] == "MistralForCausalLM": - onnx_model = MistralModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) - elif config.architectures[0] == "PhiForCausalLM": - onnx_model = PhiModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) - else: - raise NotImplementedError(f"The {hf_name} model is not currently supported.") + if "config_only" not in extra_options: + # List architecture options in alphabetical order + if config.architectures[0] == "GemmaForCausalLM": + onnx_model = GemmaModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) + elif config.architectures[0] == "LlamaForCausalLM": + onnx_model = LlamaModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) + elif config.architectures[0] == "MistralForCausalLM": + onnx_model = MistralModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) + elif config.architectures[0] == "PhiForCausalLM": + onnx_model = PhiModel(config, io_dtype, precision, execution_provider, cache_dir, extra_options) + else: + raise NotImplementedError(f"The {hf_name} model is not currently supported.") - # Make ONNX model - onnx_model.make_model(input_path) + # Make ONNX model + onnx_model.make_model(input_path) - # Save ONNX model - onnx_model.save_model(output_dir) + # Save ONNX model + onnx_model.save_model(output_dir) + else: + onnx_model = Model(config, io_dtype, precision, execution_provider, cache_dir, extra_options) # Make GenAI config onnx_model.make_genai_config(hf_name, extra_kwargs, output_dir) @@ -1489,6 +1501,8 @@ def get_args(): filename = Filename for ONNX model (default is 'model.onnx'). For models with multiple components, each component is exported to its own ONNX model. The filename for each component will be '_.onnx' (ex: '_encoder.onnx', '_decoder.onnx'). + config_only = Generate config and pre/post processing files only. + Use this option when you already have your optimized and/or quantized ONNX model. """), ) diff --git a/src/python/python.cpp b/src/python/python.cpp index 6c997c87c..6bf85d89f 100644 --- a/src/python/python.cpp +++ b/src/python/python.cpp @@ -174,11 +174,6 @@ PYBIND11_MODULE(onnxruntime_genai, m) { Declare_DeviceArray(m, "DeviceArray_float"); Declare_DeviceArray(m, "DeviceArray_int32"); - pybind11::enum_(m, "DeviceType") - .value("CPU", DeviceType::CPU) - .value("CUDA", DeviceType::CUDA) - .export_values(); - pybind11::class_(m, "GeneratorParams") .def(pybind11::init()) .def_readonly("pad_token_id", &PyGeneratorParams::pad_token_id) @@ -216,11 +211,9 @@ PYBIND11_MODULE(onnxruntime_genai, m) { .def("create_stream", [](const Tokenizer& t) { return t.CreateStream(); }); pybind11::class_(m, "Model") - .def(pybind11::init([](const std::string& config_path, DeviceType device_type) { - auto provider_options = GetDefaultProviderOptions(device_type); - return CreateModel(GetOrtEnv(), config_path.c_str(), &provider_options); - }), - "str"_a, "device_type"_a = DeviceType::CPU) + .def(pybind11::init([](const std::string& config_path) { + return CreateModel(GetOrtEnv(), config_path.c_str()); + })) .def("generate", [](Model& model, PyGeneratorParams& params) { params.Prepare(); return Generate(model, params); }) .def("generate_sequence", [](Model& model, pybind11::array_t input_ids, const pybind11::dict& search_options) { PyGeneratorParams params{model}; @@ -250,6 +243,9 @@ PYBIND11_MODULE(onnxruntime_genai, m) { return false; #endif }); + + m.def("set_current_gpu_device_id", [](int device_id) { Ort::SetCurrentGpuDeviceId(device_id); }); + m.def("get_current_gpu_device_id", []() { return Ort::GetCurrentGpuDeviceId(); }); } } // namespace Generators \ No newline at end of file diff --git a/src/smartptrs.h b/src/smartptrs.h index dbc043c55..9eab82abc 100644 --- a/src/smartptrs.h +++ b/src/smartptrs.h @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #pragma once +#include #include #include "span.h" @@ -94,6 +95,35 @@ struct cuda_event_holder { cudaEvent_t v_{}; }; +struct cuda_stream_holder { + void Create() { + assert(!v_); + cudaStreamCreate(&v_); + } + + ~cuda_stream_holder() { + if (v_) + (void)cudaStreamDestroy(v_); + } + + operator cudaStream_t() const { return v_; } + cudaStream_t get() const { return v_; } + + private: + cudaStream_t v_{}; +}; +#else +struct cuda_stream_holder { + void Create() { + assert(false); + } + + operator cudaStream_t() const { return v_; } + cudaStream_t get() const { return v_; } + + private: + cudaStream_t v_{}; +}; #endif #if USE_CUDA diff --git a/test/c_api_tests.cpp b/test/c_api_tests.cpp index dd8627732..ab5bfc169 100644 --- a/test/c_api_tests.cpp +++ b/test/c_api_tests.cpp @@ -50,7 +50,7 @@ void CheckResult(OgaResult* result) { TEST(CAPITests, TokenizerCAPI) { #if TEST_PHI2 OgaModel* model; - CheckResult(OgaCreateModel(MODEL_PATH "phi-2", OgaDeviceTypeCPU, &model)); + CheckResult(OgaCreateModel(MODEL_PATH "phi-2", &model)); OgaModelPtr model_ptr{model}; OgaTokenizer* tokenizer; @@ -121,7 +121,7 @@ TEST(CAPITests, TokenizerCAPI) { TEST(CAPITests, EndToEndPhiBatch) { #if TEST_PHI2 OgaModel* model; - CheckResult(OgaCreateModel(MODEL_PATH "phi-2", OgaDeviceTypeCPU, &model)); + CheckResult(OgaCreateModel(MODEL_PATH "phi-2", &model)); OgaModelPtr model_ptr{model}; OgaTokenizer* tokenizer; @@ -180,7 +180,7 @@ TEST(CAPITests, GreedySearchGptFp32CAPI) { // And copy the resulting gpt2_init_past_fp32.onnx file into these two files (as it's the same for gpt2) OgaModel* model; - CheckResult(OgaCreateModel(MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp32", OgaDeviceTypeCPU, &model)); + CheckResult(OgaCreateModel(MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp32", &model)); OgaModelPtr model_ptr{model}; OgaGeneratorParams* params; diff --git a/test/csharp/TestOnnxRuntimeGenAIAPI.cs b/test/csharp/TestOnnxRuntimeGenAIAPI.cs index 606db4023..cd3ba82d1 100644 --- a/test/csharp/TestOnnxRuntimeGenAIAPI.cs +++ b/test/csharp/TestOnnxRuntimeGenAIAPI.cs @@ -32,7 +32,7 @@ public void TestGreedySearch() 0, 0, 195, 731, 731, 114, 114, 114, 114, 114 }; string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); - using (var model = new Model(modelPath, DeviceType.CPU)) + using (var model = new Model(modelPath)) { Assert.NotNull(model); using (var generatorParams = new GeneratorParams(model)) @@ -76,14 +76,14 @@ public void TestGreedySearch() public void TestTokenizerBatchEncodeDecode() { string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); - using (var model = new Model(modelPath, DeviceType.CPU)) + using (var model = new Model(modelPath)) { Assert.NotNull(model); using (var tokenizer = new Tokenizer(model)) { Assert.NotNull(tokenizer); - var strings = new string[] { + var strings = new string[] { "This is a test.", "Rats are awesome pets!", "The quick brown fox jumps over the lazy dog." @@ -105,14 +105,14 @@ public void TestTokenizerBatchEncodeDecode() public void TestTokenizerBatchEncodeSingleDecode() { string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); - using (var model = new Model(modelPath, DeviceType.CPU)) + using (var model = new Model(modelPath)) { Assert.NotNull(model); using (var tokenizer = new Tokenizer(model)) { Assert.NotNull(tokenizer); - var strings = new string[] { + var strings = new string[] { "This is a test.", "Rats are awesome pets!", "The quick brown fox jumps over the lazy dog." @@ -136,7 +136,7 @@ public void TestTokenizerBatchEncodeSingleDecode() public void TestTokenizerBatchEncodeStreamDecode() { string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); - using (var model = new Model(modelPath, DeviceType.CPU)) + using (var model = new Model(modelPath)) { Assert.NotNull(model); using (var tokenizer = new Tokenizer(model)) @@ -144,7 +144,7 @@ public void TestTokenizerBatchEncodeStreamDecode() Assert.NotNull(tokenizer); var tokenizerStream = tokenizer.CreateStream(); - var strings = new string[] { + var strings = new string[] { "This is a test.", "Rats are awesome pets!", "The quick brown fox jumps over the lazy dog." @@ -172,7 +172,7 @@ public void TestTokenizerBatchEncodeStreamDecode() public void TestTokenizerSingleEncodeDecode() { string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); - using (var model = new Model(modelPath, DeviceType.CPU)) + using (var model = new Model(modelPath)) { Assert.NotNull(model); using (var tokenizer = new Tokenizer(model)) @@ -196,14 +196,14 @@ public void TestTokenizerSingleEncodeDecode() public void TestPhi2() { string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "phi-2"); - using (var model = new Model(modelPath, DeviceType.CPU)) + using (var model = new Model(modelPath)) { Assert.NotNull(model); using (var tokenizer = new Tokenizer(model)) { Assert.NotNull(tokenizer); - var strings = new string[] { + var strings = new string[] { "This is a test.", "Rats are awesome pets!", "The quick brown fox jumps over the lazy dog." diff --git a/test/model_tests.cpp b/test/model_tests.cpp index e3fd7e0ab..90655cfed 100644 --- a/test/model_tests.cpp +++ b/test/model_tests.cpp @@ -16,8 +16,8 @@ std::unique_ptr g_ort_env; // python convert_generation.py --model_type gpt2 -m hf-internal-testing/tiny-random-gpt2 --output tiny_gpt2_greedysearch_fp16.onnx --use_gpu --max_length 20 // And copy the resulting gpt2_init_past_fp32.onnx file into these two files (as it's the same for gpt2) static const std::pair c_tiny_gpt2_model_paths[] = { - {MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp32", "fp32"}, - {MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp16", "fp16"}, + {MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp32-cuda", "fp32"}, + {MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp16-cuda", "fp16"}, }; TEST(ModelTests, GreedySearchGptFp32) { @@ -116,8 +116,7 @@ void Test_GreedySearch_Gpt_Cuda(const char* model_path, const char* model_label) 0, 0, 0, 52, 204, 204, 204, 204, 204, 204, 0, 0, 195, 731, 731, 114, 114, 114, 114, 114}; - auto provider_options = Generators::GetDefaultProviderOptions(Generators::DeviceType::CUDA); - auto model = Generators::CreateModel(*g_ort_env, model_path, &provider_options); + auto model = Generators::CreateModel(*g_ort_env, model_path); Generators::GeneratorParams params{*model}; params.batch_size = static_cast(input_ids_shape[0]); @@ -158,13 +157,11 @@ void Test_BeamSearch_Gpt_Cuda(const char* model_path, const char* model_label) { 41, 554, 74, 622, 206, 222, 75, 223, 221, 198, 224, 572, 292, 292, 292, 292, 292, 292, 292, 292, 0, 0, 0, 52, 328, 219, 328, 206, 288, 227, 896, 328, 328, 669, 669, 669, 669, 669, 669, 669}; - auto provider_options = Generators::GetDefaultProviderOptions(Generators::DeviceType::CUDA); - // The ONNX model is generated like the following: // python convert_generation.py --model_type gpt2 -m hf-internal-testing/tiny-random-gpt2 // --output tiny_gpt2_beamsearch_fp16.onnx --use_gpu --max_length 20 // (with separate_gpt2_decoder_for_init_run set to False as it is now set to True by default) - auto model = Generators::CreateModel(*g_ort_env, model_path, &provider_options); + auto model = Generators::CreateModel(*g_ort_env, model_path); Generators::GeneratorParams params{*model}; params.batch_size = static_cast(input_ids_shape[0]); @@ -215,8 +212,7 @@ Print all primes between 1 and n std::cout << "With prompt:" << prompt << "\r\n"; - auto provider_options = Generators::GetDefaultProviderOptions(Generators::DeviceType::CUDA); - auto model = Generators::CreateModel(*g_ort_env, MODEL_PATH "phi-2", &provider_options); + auto model = Generators::CreateModel(*g_ort_env, MODEL_PATH "phi-2"); auto tokenizer = model->CreateTokenizer(); auto tokens = tokenizer->Encode(prompt); @@ -254,8 +250,7 @@ Print all primes between 1 and n std::cout << "With prompt:" << prompt << "\r\n"; - auto provider_options = Generators::GetDefaultProviderOptions(Generators::DeviceType::CUDA); - auto model = Generators::CreateModel(*g_ort_env, MODEL_PATH "phi-2", &provider_options); + auto model = Generators::CreateModel(*g_ort_env, MODEL_PATH "phi-2"); auto tokenizer = model->CreateTokenizer(); auto tokens = tokenizer->Encode(prompt); diff --git a/test/python/test_onnxruntime_genai_api.py b/test/python/test_onnxruntime_genai_api.py index f644fa7bb..6346beba2 100644 --- a/test/python/test_onnxruntime_genai_api.py +++ b/test/python/test_onnxruntime_genai_api.py @@ -13,8 +13,7 @@ # FIXME: CUDA device does not work on the CI pipeline because the pipeline uses different cuda versions for # onnxruntime-genai and onnxruntime. This introduces incompatibility. -# @pytest.mark.parametrize("device", [og.DeviceType.CPU, og.DeviceType.CUDA] if og.is_cuda_available() else [og.DeviceType.CPU]) -@pytest.mark.parametrize("device", [og.DeviceType.CPU]) +# Once this works, cuda converted models need to be added (ex: tiny-random-gpt2-fp16-cuda) @pytest.mark.parametrize( "relative_model_path", [ @@ -22,10 +21,10 @@ Path("hf-internal-testing") / "tiny-random-gpt2-fp32", ], ) -def test_greedy_search(device, test_data_path, relative_model_path): +def test_greedy_search(test_data_path, relative_model_path): model_path = os.fspath(Path(test_data_path()) / relative_model_path) - model = og.Model(model_path, device) + model = og.Model(model_path) search_params = og.GeneratorParams(model) search_params.input_ids = np.array( @@ -61,12 +60,11 @@ def test_greedy_search(device, test_data_path, relative_model_path): sysconfig.get_platform().endswith("arm64") or sys.version_info.minor < 8, reason="Python 3.8 is required for downloading models.", ) -@pytest.mark.parametrize("device", [og.DeviceType.CPU]) @pytest.mark.parametrize("batch", [True, False]) -def test_tokenizer_encode_decode(device, test_data_path, batch): +def test_tokenizer_encode_decode(test_data_path, batch): model_path = os.fspath(Path(test_data_path("phi-2"))) - model = og.Model(model_path, device) + model = og.Model(model_path) tokenizer = og.Tokenizer(model) prompts = [ @@ -86,14 +84,13 @@ def test_tokenizer_encode_decode(device, test_data_path, batch): assert prompt == decoded_string -@pytest.mark.parametrize("device", [og.DeviceType.CPU]) @pytest.mark.parametrize( "relative_model_path", [Path("hf-internal-testing") / "tiny-random-gpt2-fp32"] ) -def test_tokenizer_stream(device, test_data_path, relative_model_path): +def test_tokenizer_stream(test_data_path, relative_model_path): model_path = os.fspath(Path(test_data_path()) / relative_model_path) - model = og.Model(model_path, device) + model = og.Model(model_path) tokenizer = og.Tokenizer(model) tokenizer_stream = tokenizer.create_stream() @@ -118,12 +115,11 @@ def test_tokenizer_stream(device, test_data_path, relative_model_path): sysconfig.get_platform().endswith("arm64") or sys.version_info.minor < 8, reason="Python 3.8 is required for downloading models.", ) -@pytest.mark.parametrize("device", [og.DeviceType.CPU]) @pytest.mark.parametrize("relative_model_path", [Path("phi-2")]) -def test_batching(device, test_data_path, relative_model_path): +def test_batching(test_data_path, relative_model_path): model_path = os.fspath(Path(test_data_path()) / relative_model_path) - model = og.Model(model_path, device) + model = og.Model(model_path) tokenizer = og.Tokenizer(model) prompts = [ diff --git a/test/python/test_onnxruntime_genai_phi2.py b/test/python/test_onnxruntime_genai_phi2.py index d763a72d7..e2a996a37 100644 --- a/test/python/test_onnxruntime_genai_phi2.py +++ b/test/python/test_onnxruntime_genai_phi2.py @@ -31,7 +31,7 @@ def download_model(download_path: str | bytes | os.PathLike, device: str): def run_model(model_path: str | bytes | os.PathLike, device: og.DeviceType): model = og.Model(model_path, device) - tokenizer = model.create_tokenizer() + tokenizer = og.Tokenizer(model) prompts = [ "def is_prime(n):", "def compute_gcd(x, y):", diff --git a/test/test_models/hf-internal-testing/tiny-random-gpt2-fp16/genai_config.json b/test/test_models/hf-internal-testing/tiny-random-gpt2-fp16-cuda/genai_config.json similarity index 77% rename from test/test_models/hf-internal-testing/tiny-random-gpt2-fp16/genai_config.json rename to test/test_models/hf-internal-testing/tiny-random-gpt2-fp16-cuda/genai_config.json index 7dfc3088b..f57adf9a4 100644 --- a/test/test_models/hf-internal-testing/tiny-random-gpt2-fp16/genai_config.json +++ b/test/test_models/hf-internal-testing/tiny-random-gpt2-fp16-cuda/genai_config.json @@ -7,6 +7,13 @@ "vocab_size": 1000, "context_length": 512, "decoder": { + "session_options": { + "provider_options": [ + { + "cuda": {} + } + ] + }, "filename" : "past.onnx", "num_key_value_heads": 4, "head_size": 8, diff --git a/test/test_models/hf-internal-testing/tiny-random-gpt2-fp16/past.onnx b/test/test_models/hf-internal-testing/tiny-random-gpt2-fp16-cuda/past.onnx similarity index 100% rename from test/test_models/hf-internal-testing/tiny-random-gpt2-fp16/past.onnx rename to test/test_models/hf-internal-testing/tiny-random-gpt2-fp16-cuda/past.onnx diff --git a/test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/genai_config.json b/test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/genai_config.json new file mode 100644 index 000000000..f57adf9a4 --- /dev/null +++ b/test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/genai_config.json @@ -0,0 +1,29 @@ +{ + "model": { + "type": "gpt2", + "pad_token_id": 98, + "bos_token_id": 98, + "eos_token_id": 98, + "vocab_size": 1000, + "context_length": 512, + "decoder": { + "session_options": { + "provider_options": [ + { + "cuda": {} + } + ] + }, + "filename" : "past.onnx", + "num_key_value_heads": 4, + "head_size": 8, + "num_hidden_layers": 5, + "inputs" : { + "past_names" : "past_%d" + }, + "outputs" : { + "present_names" : "present_%d" + } + } + } +} diff --git a/test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/past.onnx b/test/test_models/hf-internal-testing/tiny-random-gpt2-fp32-cuda/past.onnx new file mode 100644 index 0000000000000000000000000000000000000000..c47a93275fab54c33d41adc85baf6200c7edb058 GIT binary patch literal 578141 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
    >Tv;u~3y+fuC<|W6w1y!@V_ku;V@N{23%* z|1?RAqP8^!6b&~t}vLLR^*~8hPp+ldNE@&Ou0XMqWp+i?1d|9*;FYC^L z?jk^ZIib z9=xl-C?|@M^D`?k!)r8*ATih^Yein39EPgP;yJ@tq{1PeW=OZ4L+`y^z*XFvkIMS7 zP?0zqJ}(uAQuAq~F?~#hrLs9>s<>j>RZTYLZZmhp(H+!o+8XMGU^3dW;wt3Ha!YU2yuxuGUb9qn5+8Q;&d$CRC2a7Lwx(4j{_=x#k~%{>5FtA&|E z4OPhh`XG)e&4GU2dedu>9&sC#jN-Y6$s^wWUsZ=WyI`9Xox^7WtKxxEQJG6NkGzZp zlRpr*#zbm!^aRJCn74=F(9h8g0u*|~0lD4pz$YpU?}sJhp|o&rS{L69GO1xlNGtT8SwUQ)j*y#< zH4r}HDlLB@Mf0^Caoq+X?v^MeTC7q)XY5HrW7(PwM>GQpzbHBFAFgG1xAF~D;zY*Bi{owh8DyfdCbGjANlxqTZk z+%by?gxsVDCQW8V#DqDft~a?^6J^mqt{(FQm!fk)J4Tu%akKSLgUQI@VC?V|?X9}8 zcYQ2Ja?Kv}oEZp)i!>Q?T?t$)G#%=!@}MW&g~aB6A}=rdlZ}f$!n+`PPNfg@oO5%Sds#l_`;y=r5JClv{`LO zHI$hz4K?gzPR>kAlsGNO*cywWuwo?^8D-$rU1>yerW{jQQ+!^hguVEdwq)`Z((z!?fB)Z$_22@!Uz<7y1KFp7zbJz1d=ZjNPAH_2vEX_Roj z0Y!7}L*WNij!xatPjw2WBb55i-k%3dts_~ zAE|Wd#Jkg+$>YLQc;8t{Y|kAfulH;K8LuE3Hq9C&Udv(GVSCgwe}&t4J1f>fm7tCT zxLa3}u`{a0hEHG7bnhon*Aii^E(~F=pL>BSaoKp}xgnGAbS~K2PD0-iw}`0uQ06eb z1BG7%+59COkgB;zn7(TqY1y+@hKANK?&Wyg_IA5fqU365`Ra=sRaUWw^M<4N!=cPA z`68H7pG|{CniH#>avBil1}|1DgR@I!;1LB0v@%l1YU91o8q+NDt~iIGIRE{hzx%!`}jiySv8=+1M%L@oymmnPw*ay|wWAEDZ% z>qOGm1NlmHV9AauOz^FzvC_emc`=DutkQ(x_lB_Vm3(PaPa$S#bGZjszlOFiQ>miM zE3g$lo~k{n>VLIz_U%bHZ2;oLpl(hBXOJj1S~fg4KI%WhPx)d zf(9956uRcFz4XB?Q$btEO3?-5&00Q3Yym^0Mhobjp#k=rj8RHJiru{bBX_J`7)MHN63EDlfrfAn#H(zfJznmxAwHIq zGhds=DeR&pW!s3hKohPWw;SE|34_O-dFURpAL|b+f~}QOY=HZ2h+Wx*hFTG@-NBFK z<>|4P3w7D4j-n8^suXAP?WbnTYRDsVL+D;GA2#yNhP}9W78(>vp~yK2cZMf`>is>` zz(O5NWDG6UHSW`H<1f%B?*rfl-i=5m zk7T0Z^llw8bGZZz{Thu8I`gsrLOscNIR+-Cf8^SI@nXW3KBf;03$abt7L$k%N)0T9 zpp#eeP(v9c{FcSZn0*8ubQbY`i?oxIsnJa}T2)wYUrAucpC^i6L$N!u5#tZHqgD4I z7`?uVEN;lbj(PitShrGzV3H{^!E2!YMgkEv*bdv$K0))oouoQw6Ml)6VIHi0iQD>J zF>0eX_BX47t~l>_!>KILiyzJ&9^XX@CWxZj=Stl7QIypa8jETT&M4Pwg^Ks@V941| zG=Il2SfdmJGYouin->?QR?G%Cr^95%z9cJls^cYYC1=(9^AO{Gf+&`rp$jLCWcNny z#5luPl#aaxF=IWUHQff3l$ufDMIDV$yh3$@DmYU2IB@1_GCn$H4xyDs=%KoUYBdQm z^DRCTzrZ4*yP49tH`JMh`A+oXDHe>Kr-QV#J{Za_fvdcAkES-FX7O8Mt)z!H$IByE zD+a}+wb|XG7qG-H8Im(2XtQe$ty!{#2A3Jpy9M*`@VyDpRd@`0FYxxj1Uq5)rEtuD zr$ADfaj0=YljQsH&LswoVHc}iBMve1r*yb)awE%d3b<2RaBwdg%S|Ji)gp_ zAmM)=iP1Sz@Y9GdwA8^80_#)gnFNZ(VTb7BJ}CsP2$U%HfU9K*?Bz>o%1_ z+?{DuVb=~KYPS=y_9%%~nSe1DBcNyDH4I5OiqorB!08v`r~rQ$47ox%eb&o(>-Lq{ zSmzBJ&5lCfz&bKsI1nyNJ|%;eyuk&#Hb9<11yn|?19z8k7&z=GDOjcpyQLa2<;oW7 zqi@W3zK=w2(<7+yWeU28k0gdOGQnhn7!&{X0IoPa4(G1PrNho_!+{&Tv+B7LjK6j- zx9wv+&?u zIbXubhg1RPezz={${h`9aT&y;R)nd0(FlS;mvE-fR9ySw0KKl1$sEu=8F$Q-ya*mHil4#AP7GOWnzsUWgkgULNSmcb9DkPDk=wrUa4 ze-uPsEOnq4c)5?}*B8-I&IzAP--o)=fS$1*;n^)mI3p*$^eB z`rI;NIe9GWeYb#Y>yV`qPQYG?=jAOfSka*Cl4$qhD>whN8g^@{Fon-FL5k0ezTuW( z{K;$_@Q{ULpU0qbqOf#j@g%)fdV; z{}NlV{$yE&@Pjq*_|7?e>0%Ct=jX%i_@kg*SAqo>C(s-26R_{oT{7DBE|H9B=jb|B zf_X+Filp9x{MK` zk#_Pj%z%`H?Zyp0ad2eT2fFRzX>e3*nOABMsYOFV? zs5nEy7#Fr_2v6(Lw(xZDd2G79fxalzCs~GFWN9pgQ@4R!C4fF|DfGSR2&U|U1TwrF zzVb^`(rB|DdiwpqddNHCIP@lpymH>ufxJD9wj%|zIDkPd?) z+!!SXnGNrWP**Ib7jGvvp*y&7cKp;K`z#uoNw5*y523!#15U%{#cUaw&J^@`LW=rb z@C-bOpJPjTxu-4MO*h-g)f3wA+DsE~Zm-0u8HuE3q6M*yi9rvIwXl8&66da9YSLXy zWAJ%VieLKli$tMos7oqN6F^RdcnyhwBgM|_;WTJ-?F=op_l|(`{ zpEo-vQi|yv>H;t01#v^B9ImKtrV;z(*;}^xyx%x+(SOoa(qcFVtNmS3Co-10d?4H# zgRa7n{u2-#Xif{%uMk#+OPoE`IM-b%IdC-_f~H<4PPayLMo(*nG~tgBDPaSS^Ng5^ z#(7Ly_zCnD$cDL&(>N1H+SAiB#MwO+qp%3K!1SShv~Uy>9Pi1Ee>Q@dOrE2RUwMTN z!-wI&)sv{xXFxwF9i9J!p)>KOs*S=pGK36;N`?>>GK9i?_wlKeq9UnCNpn#u6cs{d zLWU4ZiijjC+;<;EXrRbY$xs>6sEJBN-}wXXTKC?y&OUoT`+0s_D2xR5iLn<=|IntB z1YQnCV$|c z-hf~rrY{H9n03X%+Z`Y@e;;3cI2A_91TgXVBiO4`1p$SxDg*aOVBk)|fOR;sskh;$ z(tn`;RUI=@UxVS@d?;0&&aC+1#Uu}_v6nnjK{A^2clM=F&%VbPqWT)kd!N%(Ygxp3 zkEnOZ5SWKs@WXF~f{wv48nrr~wE9=0G^g9q`KNH_#!PhH8;aV_^)Mm86K1b;!k7=? zQ1iW-q;dJ+oL|#e&5(;U+*+2I9eEZmn>Im^z#sbhNG=$7Q%G&PfhCrIsEa`)?n;=8 z^=1OBPcXL|kIRB@$`e>^G|-joNRvay_q zt%`$XnUT~pGy@+PyW*X%T(-FD9Clnj4a}h&lnSXL1y6(VlkXyE`ZJHsznF@tPbKM$ zZUtO=))qz1j1Zj%(yXNYUFz?p#hMO2G!J=pp9~*ciPdwt{Q8%FIJ_qljrWXJRtL9Z zcG^UCf2c00e0%}CRIBmuTn09liSa}%gs5jm3H0-9VX^id_%cO`iShc!uf5fW>OXhV zk3okpec&v#6w5N5b#hEYy#jUZSlO7MOqVEw%mqcTsIM40mnM>j83&=rI+HL5e6i`SFe`5| z0v>Z+`OA)ru?_cg@q&;bt=qN)KZjY9rR;o+KAH|;fAyK(w;$=Cn+K%sc}K}VAISSB z%xp`##ZNq92Adifc9%Pg_aelY4sJGN6jIRb{xr7fI@c2wim{~%;h=ALmwIQLFvkPW%gymU4q6?azg05sIwDZaySvkD$$NS7=X(2k$9A;ID!;Zc#Z;#tsHU zt*bsbc}xTAg_@WXszRfhq)^DupX*HmKx)=~ntQ&3V%Tj|I2OWBc%lw2Z$5*O&{@u_ zqeR{WMPe8Z(ml;V7(b~AUS6EWDlF>6g9?eTCL%)$ZaK!& z%Z)RbLS1ooirEy@&$$eacl*%x?mY6ydN#BFR5=K^?;~?|MdNh6R;YUP2~4&hC9AV~ z;S1+!?rczEKTC{JxvS4;ftg_-Chz^{_E>ld#7Ij-# zHLaK=3O|A$$vODaW-1%g91B;&u7YdSN3h&}0b74Wk$2~evGB!ny5y4pv+FL8z6^~q zA5C41`vW6*GOR0f&v;CR6=FcWN)A5!3P-b_$~5}HGoIVKWh7kUJ5M*U10rT0ARcXj zXzu0(dpB`2Nb@D4(>@5zLCMhKposz}{}98nvpj*$IQZ7^9qre0Gohalp7<=p{@GbU ziaRgi(%DmpW7{|gTyf-h0Oxpy0+H~3ULefb83hy0?ZK4$?s#sd9LL7&05m6t<{aq+*i*qYzNFIt)k!n#s8yviL{dpxDoIT{|Wv_R|jbn`zhX?SCD zJ?=_t0P(PKcx-Z&*X_2N_dehe(_15%aPaU6YLI5ZcTG`XtFt1Y=j9~`_A=lyVFT1BKO8Pd z?V^snv@1-EOSdj%fm3b7tLia;dh0eF51M`Oo)EZ=emB6|4fWiM}H!5+Z%$dDA;YNBzCifaL&IkIIe6=-FEjAwJHnp zNm`tB=l+|1F9RBwbDd1rXd$l_|HQRIH=xcu5*Jq_;{utx{0C!JsJ>|wpUe~_zaGD) zl4~Y2yZ3#%3K*c9XbfoWyo@b2;o%+Fi4c9+mrv z5w;OjGbIRSC)wZx;ss9Mw!+xjT#_K~Mbs8Op%?!Ov!!$I;oJ+6#CPXHR@3G>{qi>& zlz4CO0SMr8VJ<^7^9tU5a|ZN&Cz7KDDcB+zM@fn$!51^(p;<3HtXKgeB1zP)QHYe9 z#=&B4uiSs2jijk>Aswb`K!3R+fARfTR1ztH+!r75@5oC~lXyb@=_G)~$12Pktbu~- z+rarhV>n}d4!-Yd2aT3?@asK>(yv`0>qISjJ@|*cqSr`b;=8%!00EtoUYq_L}2KEWa7dm-4*NlQ`zfuU@{6s5^I5mVfhx zOX5=O9BwXIm?a2aqqQitKNg2ps$%l?6DW0hF;DdQb>7mXbtLCoHm8f+K!L^yu<~UN z3C(V&r)|=3{QV>H;Qn){2);yRzFWcS^bz{nFA5gy4(4U~p1~7Wg`u*T5Bixi&40;$ zrswq5;f7U(xFU5IncEcupC`!SDgRR_dFmI+WVb`1$~X>cd85!PS)T2o`Anc$AQVYH z!VS687@2H>@dZYV|HX-LKrs<7&1i+JgG%sTbPm=!M^bH}MpC^gh(54a#eIVxaY_e| z)1qr(Dlw!&M;g(kxfJ(}w1LBrDyPQ^G3EmTtjfEu^!xiipz*E(&rPz#s0DA~UE(m= zpB+RdUkt;;w+>+XOLefDD*_X~y+?uITJm7xdo&P|1&@0V=+`BJuH<qc@;soUJyUxir@IIOkR>3C$Syt)zQVjW?XZ~xA3A^3imrqS)Q8eK%J$!CCJ7^#h*PsIQ5}S($vRi2SkGC*A>;*q6Vg-7yD1fNfx2a^mIWg0}fpEeB`Z#t# zic&HRz6kYU)t@vS27JU~IMM`Dc!RzHqk`TO+m^LZH_KpxtesKZW%WF_bLV%Un z&_FdLeiCtGX?By^01hq4DW&t#te z0gb1nnC4ZUAYxuk-^B?rp+jPSrmIC!1o&@O|7IRs@efKSjF*6Hsnq8{8PMhbzYfSveU$@RN^# zbt-qrUKNh%liY!h-^$_cj)Tmr4_EMx=@RDh40|+}&gRXY^2mIt=q0*k`6RY0X9Hc8 zEyxV?^$?|;#YDt=I<#D}hPQ+CZVE6e*pY_9o!Gz@|#l#5p-&_wz0|49p~xrr2T6{uQJ}UJ`Q`-zSbv5$IW} zOcQo`64f_#sQWq$w<(yx>?=uFDJjA>+Y#J7{2s1OyMXc){b+j-(Dq3e=yLTrXL11E z{Lsw{%KS)!_qRjQH77D*{3CA9_)ISAC_`b_d196v0`Z#;;MN+#`M`rocZe{qt+s+& z=CdIuOpvY9Ttmc4u3)q=!+89j$CA}7eA&~7jg@9cwh29|K#Ck!IRPoi%heEC4}ie_}MagnA2e{r;)~Y;H}!?653w z1Pzi^hcsA520MB*G3dZEDnD5SL(JqVyq+8*ekxaa7Zl4#a7YuW=rEHqF{4`jbRZ!Es-MrMhnM}Jcel-8c0F0f8>`XAh=d0utZyBJ99+iz{%(3h;0-)m z6AKqbK7!C;eL6oU6WloVm*Le#{Dz>ZcKM?}%#8Fh38 zr$;b`&uBmQPMtE(mDUF)(-uc3sGsWzvluA|SYbe)g?oUUMiSOr-@-CZKV$@AF#7Xj zpcukNS*@qRvgHt`EDUpOmh#e1Tmx~LLSp|gi^ixrqcm)J$1 zFJS}zPwYW$&>0^rY@m)CZ^5@EYtS+~5L!7j8ft4G)4PO+}sHE`&ES&QTjWSB{>E0O37$2v?601S!uBs{P=Zh+n zvhmX+DK@}xB@CHZL9nMAyxAdNYYxO)ZeR>P( zo0afF_WcJ6(Z^6;<~Pkdq{ZB*D(4qiWx$4aLEs@(fb{G>tUWJ}+wXC4!rrv1Sfp^j!HY1@_d_~@m`B1qW<}tknrpi zKhL$2{K@}_p}#V*p>YlIO0&W0w_?o4*N_Y>GzY$G*KD6l@pBtX_`Ep$s(k| zrHv>Tb6hx^SYW1m^_yH6=?X-8%>kpMIxS_8oLoZx?+1mQ032f~g;;w1teH!4H>KlF9}N z=6R|iN+#|>fnC=jZ*vNr0Dkal7N_HHHKyu6voU${By-y_dvLb74{oWKkY;v|CRGK<~M-f(mcGko+Zi6anSZ_GBtQuh7PfnSf2LD zT-oh3eymdBPfI-k)!Vm0M9egDbJ|^4JA-4yZP4N0*wT)R&MyS&ZAJ1_yNJu3TjX^z z=feuS4g*L2{#WzEu}K0;Z1JN^)_QX-L;^~{?UzOeySu3qQwOIBsW*3ZXp!}sBF zyEmwI{Kq~sS00>^I;@IvYYds$-?^;tkPM6s zEr79InXq+7KF-lqA&2$1!W&H`oZKqKgnZIsPi+r{n!v4iSj-7GYnUNwhmR;|EUucZ_7cvNfc48+{3@&bQK4NwvrsL z8_*TF8%&FeWX z_i3S_D#zftb{1}A?12D*?Lq)0hKurP1fkBCHD(pws{R1ZjWwkbSa0(D$h*t}jYL8AB=d$HOrE)V>g>ddf0`4YB0joE4P;B7J*U?=L55eNy2vz*t2$E}^!Ph{H3I4Da_O*D>n=evn%RF_kS(gqkdIZ^b zTc<;wE)OvLG%6hUk4)3r#?8we^wl&A^6uU${?cuyc*fHgf}42?{#2aD)~ptyE{D?j zjawYBjXy}}i*p^g? zhTr8;ukt$9@UvmDTPA#eVafTC?$XlcNF2Ai!(Z^X86T|Q&2CsZNc;_hxExXrO^BR@ zQ#c;4h(#`T-PsEw6Q6K?J7wnI^<>_VK@LnkqlFd4zoE823}vS1vEvs%!YZF&=n5#p znAJU%p+yboF@BfIo+*JRWl~@|hpVN_QlUg>DvBLmgKH0P{>&}g$;#F#AQ#t!&-*Ce zbDaY;y$YGBVWits79RHHqndOvE?B!6Np=c(mDtCdUn9l_`6xl2n~zzI#snP7NrhT2 zd%_#bLJx<R$6d18eK|btGUdr; z=`l~;((#DG1T>oM3NP6SFh6iA>KO<_fW0d=NEJf=%*A+7ehj9q*My~7E0}LDe$XA! zHf;Ch*;p|5J9>T(A;I;5!1p>2x$3p_&FtyCvXzFoIt%C#qX>vTS_i#rhIpoV9i+keVogHx4B6gvD0zEQ@#GyI29UHl7e@G(W@HdtyMs=plKt>pk6_n`Nf) z$An##6b8ai??I985zHD5CH2=MscqOa*lT+Sxc4u(oHWJ^rLEBJzn>2MdVvY9GDPCe zar&coF11m~M`7h@X!uSKv6DiO&}*~^Rda7~c_gmy9G0T?_6zXS`B+@KIsoRZmSZ9= zN0PYMX!hzEGi=*n&PvHUAl8}lspSZheQ}u({yqy$FKr`1 z&mNM^b_>~uo}a1qGz(VEhT{>a>VdtJJX1^0L7v59oOHRFsPFyEyE$zW5f9WwhvGNH zLUKK|o@E1~Thqx=&R<+*T?Ncqd3x1K0(x}x*t&xzko(~|9UXj%k1S4+@L9j{c78N8 zAGHBP`#N4=`UR4FUf}(|uIYSYD4x#tSh8Lh%)+&Y$Gn z3b_W8r=_5Dd<~fSN8^VZHZW9cO$(&2ph>6-l$=k1n@j&-N$69II1!4AW;K!2wi`H} zZ3zduIlc7jVzB$M7@TJO0IkXTXi*`<6qu{vCh(02^#V&l?%WkBb zEkb6I3OHw|RetlHPFv$`c@Ntbu}QBSAZ}$kj;Ae$Q0*$niCsY!ZI{O1hr%GkU6ENk z`zL9ivKBO6wcsWG8T=079JfiCkS7G@407CuEiWKipJVlHc|g@;bg9>^S7e3j71R}u zAx@f&yeR=unBXDINTwI_=gd4;F(KeR+2k$@rGHEKfxl<)GJ9`;n$$<$NcufA96DL) z^GO~)^LLWtQb$>tJ0&%g`Nfx<{t~*LTAZRUTE%9=&Zj8ilTe4a|*Y= zl}!fAhhI?Q*ggEAbp^h--vU!^K6WtXctEb#Ayi}&4%(duD~B13&X6ev8T=%na`B{* zaYRLl-JqZlMRaXh%r-S<^wz2H2S4Xyjy?fN(Z8Gk&2c@$<}gmJn&1^3OI^I`Nr>ZI zW>d`$NPPT-q!cr3@ar7p!k*M}!7*@9iUSRkWd12qMN0QZL4tA)?Rc{U)7s4{?UGJF zqjdxhdFSAGj2CD{WZ{7&u^_N+A~tL(1;QT^+3PqBOGPhxt#tC@}*-r6>6DlKD(V8tOfwNE z${;oG0x0KaFt$7LsF1uAW?e{ws{``h&{c-%rAMO3vhU{Wgf`VK<_i5#FO3I3aD0Q1 zNq}k}p~CzsPTnreHnF>~?vNXFY~9NLe#;4Wzc>vS1}m{}*G*^}@kA)tj@eS%n2H4p z!0%cU>|o;IMQj$DwY}hf=z2!##$WL&l`~-4k!+gUR0%6irl5_11uNi_L}H$V@$!?h z@Z(k)+M0M9_nr)(v*JZb>r68ew_l4*3-BY20L+=YKsHW_wX^))8_sP*!EO_I6F4N zu#*CF_go~AvdtuagLIikKV#tPog$7eoB+@Fwv%%i8l=wiG+uuy#nil?3XX|w@WA0X z^vv7|OFy3^rDtNH*sPCd^n9FzNvYut-C9U~JVemJlGl=b9H#4Wp8iMk$UV_i41Bp6 zW*BQg!zvXf?WX}V%H3t4AG8^nJVDS|Xv1ytRj4lCPj@LaR0eC_pbCO3 z!S2o#NF9$wT$zh))o~Dde+@j}K0ucHs-Vr<$FNy1qH>~iDEc?8M9pJe*smvuv)?F@ z%B5jga^f-Xg3$@wdiWbLL@@s^?yx8$yRnbvWu=03{afN% zRn1d1i06N(*MnSU2%D;}nNK;EKp!+m;f~{+mh$d2nk31y4Z!h34gK-cYgI<~crMv} z#)L8}vv3)kN5WY-MjuPzxLO1K(-=pTMtZ@osgGDZxyLVPRcFq&J+ ziR-`M-s>T!l3EV0vAswCHMnh+Z0#j5Yj(U+LNLB^m@yYh6 z!s*c!x3t;C`y)Y$%W1m0y@A(3x8U8D5K{J58V}eP;zjXsocGDiL3vz7x0J>a<4+`R{hr_H3z!~pMgR^grD<+xkk6om(J@lD4+y8oXnll@i%s@ucq zYAZ*KyV_5r19JItBNMT3p(8PWa0bQ>p9Y@|0<6yUZuC){h4st1JlfMy^Rx%v(B)N2 zBL2HYT3i3((0~~}^~s_>gap59K>DSdE1T@!&h~2l+5tQ8IhCO zws+q2-MqbcpyvsmnkLPJ@xGF-CEi?)JsdXe)?;j^&S#uwZ)SQjw^4(18NT@OI&ANa z#?FQT>hIgkFFYGrvCi-n#LPXLZtc?<6E&BG;gcV_cS&K2iJ(L)j-y==|-RRm|2Ng+5sAm0&-WhF$;avqB zpGuH!Hngg&c6@=B?{7lZgj0O))G`uZyc<(g_K;0nonI=d3DbiMFsEgNN}UY@?z9JN z*fa>mSJS|Bk{M%emqFQ^?o6=ZMxw}hvT9a@;5|DBoO74cef&5^lhY#N$(lfSKp1=p z-%isvF2!BkTzt7+79H0PkkXDsFa)e z%%H7GkALloJ$_P8gGTji>et;wu9)}Rxfgy}Qik-fvSn2udn;mDaFw3-kJ=dE(^j)gYMaLnOmZojgw-xPvEDp6N{ zJ$aH4$+tM0QmJvzlB9*RXm>sc7w(s#Cbr>tZ?zz!EvkvLH)=3)|LsI!(=?v@v?|EB z_M2i#Jpa_KnK0ngyrbW{`MY(5QTFXkT00DQ=fejKzjBhK%-w^cW*=yH zuMkr!`2>QsH^Qnr=cvfH&-~lBIWND_5T+E}=VNmPX2>2PVs=XO`^G|SNRVQy@9;@p z(sQD5b24*XLX;_WOQcDE(_yS=J_MwHq7jwK;8xjB-gSHd@m~(KKP8_p6`O<))OMqN z(Q&NYqs6QcO(o3@s;DIsi*3ui;lkUCARs5r#;>2u4!067(7UVH^t=htcDigOL1uTzNYXXlY&O>Mkc z{0>Uo6RFm-iA?5lGny$>0&yf0M@0bV-;)4&!=3EA$&X3UzSK%H_d#A(Cqpxu0iBQJI>OH3ZD&q(IK90jz9}yeV67Vj!;0;*3hUQhl=GS^BKw@$USWZ2~ z`C`T~dH!2y%5i6va)Pi-JQ{ntjoIfi2_$U(VR+Tzhb1B}06mHZ6W z~wdFVim2Q_@jno6}^J;1!zf=}~5^<2g+%E)q_j zIYAah7K3W;7;)d>!*lH%v zScbh-8iQjZYIJD&H0bgFfgj%v;Ll<19>RJnnmr+y$4nb1DfOIJKm9gryOV|~ zXkCxPrX9q?>_*Q!$3t}ffb4E1ObzCU^8AwZ%lYgUCxJsl*lEbsXd?ZV4u=A zBc>2@Ljy;|jL}ru0RP@BM$=LsOg6A1M>QwFyz?I9%YWgR_RLU} z2UhuIW9pwa`nSoKs-BX@KQi0NFXf567>-Rpr~eq`?!nMkMi=3(tsl5Nw1woCU(r}K zi9aS;%6Yyy)`5E^l=!S>WurK*n_wQFjp*cEsb?Xy_66zpU&1jkI^f|E4LEGF7HLf@ z(V_Mvv|NbETG>vlUZ<1Z;fv^=+(9P2j6uaMYLtES3H{>skW!9~aAdhEqa$z`A2$C4 zvle9(y89iH9t#6uY%s~8fn)C`!=9Sy;M1v$H8$@_TW~Ro%@$%l#(O|Y_y?GpAWz)S zjL`5~kFhQ*86Iy9phkbzgWQX+PR>KnY*AGeIY6W)ITn#pIBe%1^sK7d^ zNHB&MH)EKB5zdPL39-Gx;JC3H+wWfFxk&A!1;hVUZdVZCpDzr@~!O7{s4&}8N zZ(D_3^YT%3$wjL1oJ-;UnaG$6EWk?ExrgX1FfH4@UBE0pIw^5TKat?a(fq=Kgy4XGy3tM zEUXC!ZEi#AA{TPO@idHeNirVsrr??PmAW_v;)vu{YRi2t_f|S}+1P@0udHx^9p$Y% zW=w4^G*I2*OMGEh18}~<1AVM-lI#gAVq@Q%*0oWILyb z5cbC=qI&cnUC^h-ul^2Hm`t^cr%Zxl+_NWvzH^%bo`(@Z}zcNgJKa~Wm z_=?MNUUNS3WDs&tW|YE1#f7Pb)l_6^~t;^l`W299pB5 z0U55x(YdDqbuL)&lW$go3Ok1CC66G@>oxs85(^IdrV(w?$?)0SibO0~1N+^>SdWag z^iOjYkzJ%l-(<>=0%nj16qM2pkA#_o@+%FmF#$}gf@p$-7 zjB)xw`gu*}tMpT0$<#mO{YE}%^w@}rmQV3buoTZm4q@r;S!g?uM&93?$|hXnF)p*t za}4V<;JTy^B+gu;*POn>-a~s)V;LW>WVVC4Pz#6&{KjV z<2M?h_dI*<7ET(LH!Q&Y9(icK_B&Lzd!l%NA$%6*F*^SpB68|m$#U$xMegW%FdvqRIl)3W3qBGfG$bw!c29W3FW4x=-A`%7(Goc*?owoY z)&h}S!|k!grqdFR*K;Bf94_};mmt<`KJ=>>W8@d|CmI(;A2xNL_IIVv{#X(rMRT5U_4`9XALY^RPqS;(;I#pT_J6F#kMiR^Ev&|vUu1INu-vDf{ z95#2pUykaZ?NDyY2HISC4nB$GqG#=66m&IaHxy@rbln^@m&kyG`a~?AF%!hLU83)k zG|{-T7=r7yiRs!`MDvIRUYw$iCmVgB*Zn(bG#eyRVWt@V)E-y76rgD(w`lto3sjx- zm3q8C3|Xqjz-PiK+#i|^VGDX-yyGQEZ4zNt8N|YTV zkv(qFSh)Qxq*_#wk18#o>~|2TbP4~U^+WJ}val@M>WuuiX!7i%4Ra}f9bB&c z52KDgK*Pdvj>)9Kp6C<8>L-e%;@VwwxsU?qXI;c!DPQQ;q7w39?pj=&Q-U%a=RxIN zA!#t21W)87FiPtawEqmJi);9Nd5u|k=$|TVw^HXl*!>^lH1-?H{MJCYOd=e=7K(YB z03IuGne9MHW>v%tFz1;33rqFDWIEzHFL4NJeM*!3w3tEjVT`&Gf+?Fhk6e!oR4!%V zRhAZW{Q4Qf=+6KXhZM+LTZBHUzLact^-s5 zbu_)OhPIF(p5uufOyvO?{Gc_TzrH#i=dFu`22c6QjIRsX_Au@iZA&J<{+2|Yv6tbd3D;BYlV|0db?GjC z7#QwshvB0AP%!BS+*q%MmMcA=fBQ=MhU4d&R5N7uXg+ik5j5^9CShV1(K{p@wQJL$ z@bexRHIF8?pX0D5nad0c?SVfMlbDt8i|$GP1lRx5Tq zvL1$&vZ?Ql4h)GFWe>_oF|(-(ZjK+MvsV8lBBA?vm+woW?=OFHN^~9uSPC(UMfKqS zYl3;RhB#wgT|u{Onn^xn7IFH~IK8*{C`p@j9}kU6vqIP7(MkC(OnG~f9{p;`3oV`u z&z??YghQq=0;`75-Tft|Y$w<$8iwnJJjw8eBGeTa#UbU7Ah&r2J8Zq2Jc}rVvW_ow zVV(@z78*{i4?QP4)9mo)sfU>PN*`Z;wMVo!=Df?2P~UtT$Mdql#EYx{Wr9rG%Ww4I z%<0UcW4rKS{Qx`{zK@IPEch$;7oL~plZMNaA%=a%<)Yiky|D@9)~_BFBsZ!o7_p;Pqaj*VtY>m6dspiSN0u%Qolj6LtY&N<-XyYfmCv@;|5Bq zU*mO1i;(in#c(5&As*sSVBYkX|A;e|Q1EB=pi2WeKeR zvIm`Sp9CM%weU0RHMJH~fn#oo038)XK*}Gie(uEYB05aqjAAT4D1hoNade7rB#wSl zVE-$OM7LARn0XCnAyC+a-8D56cKGSyVmWgr^QAlS3~2?;XQuE<mv;rFkCg!Pq5?G( zVo?8{CCwYw1M+7z`sY5z6D^{|_TxH?o_H1?miM5>ff%T{+76Fa#nRo}J=rAVZtyzx zjXuBrhxQ#yq|Gyf@MF{;`q!i!Dl6Qe#<2=3{5_y_w;0=RKLb>PI=EdcVRmz~2Wq^) zKvflZ(Jc;}?IrQKei(f8m&P~EC6(i~AHi7R818OVgIR@d`L6#F6jacq+9AGZzh@7=#|R1^ydyqR*gzQ&EahzzS)#UJlB9` znK&|dxq~#U*~IN6@8NE~EG{kTq@8&b4YW?OVVat}^?2>9lWWl#JWFaI4;tPz8<^rn`JS_lK9JeZZ*s+SaE_h4OODV{fSJ-t568LcMg(E zv*Eqi7P522U!u!>&y+_CATIPM?6HsK%h!G(KX&Ruz%Pmo-@1tQ>7%50`Yv;cl4K&M zAy1-zOaeW(Yef991S|b*2J00!3A|2B!&#?1p!J~-e|)-Sf=7;UcQ+@jgF_aQOjEG~WUmRp??+z2{<`^~`7#4- z$MVqY;4r*mHlmoq1yX2`Osm^|llq~*(6U(=(;Ew5s8I%wCjA4?GGn4EodLgfW?|)_ zlkh<7e~QjLnyUA0;~64DnJQF-4ADSRob%jUDMO+1LsF=e;j1){DncSd6fz`|LdXz_ zbDn#n5}_g`Ng5O?g=R{6_xrbHE$gg(?B}_L&(%fNL}_4O=UWIBHG+|ud7z~G9}Iu~ zOTFfvPkhf8ohiB|-cMMA{UMl`HB zO|B#;GoKoR;Iwa*a4^On2D;ASx47-7Hvb@q`s{>;?3+}n>k!;1y@)pX)A4A|MbgwV zpB=JJL-|(+zS26H>BNN2{|V-LHFYw(m!|z zW^g@mgFhMgPVN-A`&47)`$NF^U7%|^beYThWAXbFDR}oWAG>578IRvD=sd-#;GJs& z%FEY-$&9lQteL@+f7gR*-YeM;K@*|k%_(Y7{*Zs@`Ek5aWy!u)P$m0{-@`k5O(xS| zl+-t!V@0KmprBj{j{fSyebEohCO@j6iwu9k(Ls(4=5h>+j~s_1utUIyt_mWgWP`{X;xzC286{Q}`n%4m(>SFgjSA4p&a0>?y93K54IT zvwSfqXCABD+ROouaTvFKChOUD2MvP$gUGlQ;IG1I z$A7DF)#Z5{BT198E}3A!5t?Rj`h9QIC$in|DJ{EoL>T|~9+YmYqrTCl{56HA_>#{m ziT|zk@ zcGtvLS(lt5`KN4&zy0LB{`X+IRBm&(UDt%my#AR+cp!vLd8jPPW&D` z(3N7xsV-)Gc%3+RI1o%-7$%Nm#poF5Vsf|^DCcra#XAkcw1f0hc8LfB)AviV&L1zqEcbD^{8a$<M3zX8_m zvB1}%ZRADQ03@`%q_-kBKh$_}yVRvQZQ&4TzbrGGvi<}#kA%QaHVWl; z*ucX7&A_%(5_noof&JWcgVyF{VynC&dT9cqx}V$CT{}!1KDEH&fca>5WIpJeyeZtt zkHp>onn3)IGfbPq@sBUOr#(r_xy6Ny~8d++GN_$~Fs z;&!i~_>~B|Q06|m2N>|`1s6f{0ZYy3Ag;Oj1E(mwKvUOq{Eimx{#rd7;Oj{yv1zs`}gA7b9q6gs6=1gztpVrsMmb{;VzZP!*{uGoL<*ab!0Sd@mg3myyadq^-t zqR+|C++@ltyn*Rc{piW89mox~3#T5wG)in={0#1xfLunE+` z3sPES-i%c2Z#Luqe=eD~@f!`zNFY^d6w)L5pt7IKv_IMgHBVJgIL~D)D6i_Uk>x^(I3?tt}RWElI_@t`*pC zHA=-lq~ov4W@eH*gW%WUT-Yu84FB~-@DEmJ!=j%Ip8l$Vv9;aM^`sbgT;g`;yshB1 zM>Kh-Zi?;e5c^qdgi}SZ=lg)1u(W@>Me3ulGAMQN@)19-~yc?glTFphxB^Fn$@xOiypZ8y;B%*Zjv@p7w{V+0L=0=juRwnmi2N zvZAlK`?hpQJKX5*`6h~gyGT|xg@eJ! z0(7(-g)g1lHlEdOvZ_}ZR-IB|=lARt8pce4rm0(rhwOMTt(d@uuM8u_&hcO^Euh|- zKA@BGj66%Vh3sr)*zWj~REN%B6E0na8UKXBs8d~N&D}}-d{Z!s=V{q|Ar@@{f3C^ zmvQ316`+$4=2}1Lx7$TO8;7IE!y)Y4O^}lu zF_E~pXac0Xo5hwk2q7qI7w9{##c=`JRDUo4tR}oee{R2NqIWq6rm|ppC>+(rn`z*K z4wUYk%}A@OfZM_X;nFBW3~wsr7c~dL=HC_I)bIk@i~6X+(gV1KtwD459-+6^AdLF| zg&8_a(RIRS{8yj=lQ){6!NqE_)YF0b8DAxK);p-h_^SfmF2d_w{v72UuY+EcDp9`J zLhHZ(74G#-#ThLIu!@<=HmBSqb32c7x!PNhKeG=SN}X|h)JJqI`G86%#;9BEWjM1u zlx&{)96QUaV72B(kd)^%s}|yG<&)e_do;M;>wvrGPC`6e00GX&NM*1Q)EExI zXt5In?B@EX$`?@QrxkX;KZ~~x|D#q=cR@8?g>cy12ei z3N%I4f@5te?i%wZym1z+Nl*jaE!?(8VCO;LqC5%7iF?J*|COKQoB@rPj2IzP1j=g$Tl7G))G za~{2>3MAqwnzcLy9p=gkm8C+VUSb7Fc=}x^c6k&f&(_hWcduc^bs0v)bq3q@d@kO3 z=uhLKbHGiJMgP6BkiYz+&`my@_-vbk#?5=dp`@d5;mcC$^l%u(K5fUp2en~-#d7HPd@KxDl?HJx+zyc5 zS<-DBNgUmuK>!}6lP}Bxf!`h4Gh2i$UGf3TwzS8D4kWn**AJ30sJ@k?Na`l962qM}x`sh=!i(1CK z!{FBw;mw6wvUI`@NSirAtIU(2Oz;sFZFa!5N8K^_(k<+c%A`*f!(i#{>2Rr->zU6h zhJ#re?2V}-@cw`D{6K>%khk|0I&;~AjY*13)sbY(stkgg#`9VA@xM6!KgG27ccC^> zmYu%Qn!K3&1X{S=p$!+6u3FyV4fH1FFcN#4}G9xdl!aPq&-Cnr4d4x7Cx9d zh|u^Ir-xsiNE-EfNOyii|6(0`r{Ot z=d@u?D0!gXuYNfSkbr{~_Z3Bp&WZ zO=Rb#o6|t2By5~GlPO*H9zU-8Nw>@n5ZqQ7s=JK*fSsW)PhR>YkST#v|%+i*Il^(}X?@ty~-K|6i1s$gOdNy5n{RTwk z-lNivoMtyCiZQX$V(PyM@xznjFc`R%sYrE#|9Gc49%2vaF0X@~@A~l1f_IoZwF7e7 zCD76}2Aend1Fd$)Ro1%XnCS|z^I|1N#;fCQ-N!6 z54~t0k9*liAh@l;8m-7hjfkDFep@un)k{E`q!v_dUBsH!H^A<)OJw}SpY+yqD?GbW z3Y|q_aK1tp-o4lff>oweRayl;UVn~<&QC?d;OF$~?;NyIl4gfFJ!I!rL$>`Nx3!n< z%#5GA6u0)uVW`O@=Ir=$s8?-nzTZh9^3_;QzYaSW+AEq53;E5hg%`M68d0RFG&u=Xwf^ zbV`HGnq|xt``PS>*+FXLl!}cOab&CYcJ_vN5oTO12QzPTsOUMvWiS=V^sf@kwk9EQ zTeJm^bxD$*&YNU-1-E_o(w^CIJ_<(yk70x4Ka?sj!0ZTdm?Qq0?tgTI_bTTsXc?_y zWh(7J^`t#LB>cvgT-FO-U#7A>hP?=%zT@o;{h)bycw8*YxXG+C6Y%Fh4=7We2_(W+l zFGD=Z-EZ5V*WU&IZR0XN%T-`~Y7%-+abd$EPT>61{~^1>7xaunVBr#R>~(CRYvP3{ zyXZO8h^@wvkFPN~`!)IVek_{N2rX*cpsAQ($g4+Sqs+o?hpUL}ZF=~J8JNnI zfz_A_MASJDi~7g3xIC1Y9y$-D=N97WJ{dUole-?0}|_0Un2aL9)(?~e(MHpTHnx${mnm7s?5AB1`h zN~mnI2gNyk*7-mN>c$r1%K7$ehle`q?`WXry2qK`LRH#)Uz%Ab77N-(w+OAwC&5q6 zE5LXZp^R2KZfuwjV~-^Pk!5raN`2u->R##<*rfsY5g zGmqvNaxbAvBN(!u$}-|kg<$)w8{I>mga3wUtmfHg;6QpfX2~g{{XGenjh}+y`@f(H z)X>xix@_*v@337{nV8Ex#gDqu5b=I7Y#Xp-mnbA+=0Y)ivTrw8=TgA`-k^#5dauDr zwfi*q@K(qSIS6$h{t0hcNV2<5NMMx5GGSk$7Mp7;O&6}T!l9kv5soc;@gD8 za^MV*uwM8(Qb5M-TZVrJ_R0g&-bva2&<8_4il6YvUzj44Lg@w>e^L@oh@njOe+;@#-^ca z;Sf3FRtdb2JlJOV32dH@E$-$TE;R@9hiia%QKfZA=2$+TOIz13(%S8j7*To;^% zkf44@)v||$E_zgh+es?eRxij5mS8{kB%+di2fh8v9v5<&7;D!+=Cw{|$It?9v|l5c znJuu)Z3hHjmxZ2d^KtG%1=cX!6dgA{MhBz6;8&FlPFV>;m5-}n%@$q8v-CRDJ7s`b z1NV2|<+>ipJ7M!>b!L$qr>R#hM-4yj|DPGcQQj1AQr$^Htwo^WHrI(*7lwh)<=Cn9 zaUkxtgc&(7kML&1!ang9V!`Vn5iS?`M@zJcx2-Ib`NI)^p4EdywMQ7W*#gepJi_^E zvACODfg!1H{K3b{d=HL;R+=yiV9-Z7 z^kK;Z5^1oUdCT=U6m?rTzpED9oaV9OE}?p{B}mq)AQzVtMXp*dWU!SsOuZ zoI3RF3&MRl#Uw&Q5|_O@OZq$(A@$w@*Z+4?==G->+m5+FvUDKva!G~c8IwUmAVxk{ zQrwe!20x~2Gq&aTAo9Rx`s<-Ij_DkyZ9XqBueU}h$W>$uo=dV%Q?rN*lP|nKK91(h z%ct#o@4)R{c2IHj4heM5#F|-48Kn)q;Ct{1Ib(7G`!nvLxdlsRG!LTgrTNUXDrLs4 zM4j=>Qea#OMN$~@VRk3p*teg?Pt}Bup#%JuJ|Re? zKZEt0?vqh!&gl71#BK2fr1WbMibTDj0}Jx$2y+kF9yC0qFU{Rh$7>Ike9 zVX6A5r=;>U_xbeR#+M2Qz_ ib1HheHmN=c}V2&1gEKhvRVmWo~o%O9CxuvVcs3 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𝔏(} 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%0AC7jO}VKy7hp)X^lQR}2XoXOW>KKGT9 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ℑ1i&ivWehMt~nZ_to7V*MWIfnPV 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�~BNKVa9gHnLfmp5u#BJufJ(}FS&iXvqUD*uWy%D|~or@hAf6zeY zIp+;HgMQzAuvweSv}qs~G^fxR=ObybfHq5GvdfO`nS@<+x41L0AJ?gS%Cmkbfg4|B zBbDRkSgI2k-MSitYlk8AkrfCjiLrBkKcQh>*0f!DA1>Y8Lmi^0v4bCGqF6!*wn-l# zqbCQ+!}}G~=w2GV;dzu;Uie7w_l!fspC6>;J;e+u9@2sccr|Hti@-glcDE{B3yax237Axa0~s44b5r1Q`=LBc8nb^diWg{PdA1m zv-@b-y8E<#X%A++nSxoTW?`LC9`AR_3f5q6FsS+si*MqIuPp+DURhDMX&)asj{zV8}r;oM73 zsgF_JbOD?8eHV3~X~?^>?=^Mgp4pD)my^a0TRc7CIT7R-IZQI2=F8;5?kiVt(u*hD z`6iv~QM{!&dCyU>_ZohCnGA*RO>vOR)}2$li%Z5b@qI=YCUZMh#qAPAm?=R^A#-rv z;ZQnkJOIlxOxWE!7Vs+-87$&<-|ZH%=-wHHK{5s8?1oXYMRYz2ohrxeT;K4`xop%~ zt;sVRdkq?ozEfQxN7NZUO7-OY~1AF2NvPaAaL3P z6X)LLIJT|^Wh+}CX-W!}vbh4SzF(>AtOfXLbq+`^GlHS>SwwWf6T*Ke&Bnf0qhiW2 z{ImZqL8N9Aie(1Sf)`im)-tYZ^mZz0%^w0ZiiOighhW8!9EM7)q4CoWLvZAO#7SC? z*;phAb;5g~wZ(>7u6BZtp^7wLE(2E`t0IG1(Zs0yBPcEX4IMu?=1p@ju|6n5_U3%# zZ3_?t)1p**qDv9CC*Gqa75i|_lnAg+=9ucTIaq&X2qM$NKz2$B98C*`vraPbZM+q> znzfO_9bWt+(FNG@$e7Eqyoc7^+o0&&XOIw&g6g6S-bek3IPc0dcr2aFUoRXAyZvXg zqi`L>M8e>z?qA}$DI5mNXW&ok$!w6u7#64c;gzo|F{F$_#Vlasf*B&=wwP>sc?bXA zdT27YQIj3Z<9-)Q9;OIo!mkA-_{d9vl{%t?^FR8*$3|h+?ZHIMn%0D6avN}#!3Z*2 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 z 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 literal 0 HcmV?d00001 From 57c55396442354dca5326eef974af06f125e65a5 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 13 Mar 2024 20:49:58 -0400 Subject: [PATCH 20/23] Cjian/rc3 (#198) --- .pipelines/stages/jobs/steps/nuget-win-step.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pipelines/stages/jobs/steps/nuget-win-step.yml b/.pipelines/stages/jobs/steps/nuget-win-step.yml index 47cb4c338..6d63c3e60 100644 --- a/.pipelines/stages/jobs/steps/nuget-win-step.yml +++ b/.pipelines/stages/jobs/steps/nuget-win-step.yml @@ -16,7 +16,7 @@ steps: DisplayName: 'ESRP - Sign C# dlls' - powershell: | - $VERSION = '0.1.0-rc1' + $VERSION = '0.1.0-rc3' nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop genai_nuget_ext=$(genai_nuget_ext) ` From f43f7b0ba141876d80eb0fa8f0202d0c45c71bd4 Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Mon, 25 Mar 2024 01:25:13 -0400 Subject: [PATCH 21/23] Rc4 cherry-pick (#226) Co-authored-by: kunal-vaishnavi <115581922+kunal-vaishnavi@users.noreply.github.com> Co-authored-by: Ryan Hill <38674843+RyanUnderhill@users.noreply.github.com> Co-authored-by: Baiju Meswani Co-authored-by: aciddelgado <139922440+aciddelgado@users.noreply.github.com> --- .github/workflows/linux-cpu-x64-build.yml | 2 + .github/workflows/win-gpu-x64-build.yml | 8 +- .pipelines/nuget-publishing.yml | 13 +- .../stages/jobs/nuget-linux-packaging-job.yml | 51 --- .../stages/jobs/nuget-packaging-job.yml | 77 +++++ .../stages/jobs/nuget-win-packaging-job.yml | 70 ---- .../stages/jobs/py-linux-packaging-job.yml | 8 +- .../stages/jobs/py-win-packaging-job.yml | 30 +- .../stages/jobs/steps/capi-linux-step.yml | 63 +++- .../stages/jobs/steps/capi-win-step.yml | 36 +- .../jobs/steps/compliant/esrp_nuget.yml | 31 ++ .../jobs/steps/nuget-releasing-step.yml | 49 +++ .../stages/jobs/steps/nuget-win-step.yml | 18 +- .../stages/jobs/steps/utils/capi-archive.yml | 6 +- .../stages/jobs/steps/utils/download-ort.yml | 2 +- .../get-nuget-package-version-as-variable.yml | 42 +++ .pipelines/stages/nuget-packaging-stage.yml | 28 +- VERSION_INFO | 2 +- src/config.h | 2 +- src/csharp/Generator.cs | 5 +- src/csharp/NativeMethods.cs | 2 +- src/generators.cpp | 47 ++- src/generators.h | 14 +- src/models/input_ids.cpp | 10 +- src/models/kv_cache.cpp | 16 +- src/models/logits.cpp | 12 +- src/models/model.cpp | 27 +- src/models/model.h | 13 +- src/models/onnxruntime_inline.h | 2 +- src/models/position_ids.cpp | 18 +- src/models/whisper.cpp | 4 +- src/ort_genai_c.cpp | 22 +- src/ort_genai_c.h | 11 +- src/python/py/models/builder.py | 298 ++++++++++++---- src/python/python.cpp | 80 ++--- src/search.cpp | 70 ++-- src/search.h | 10 +- src/search_cuda.cpp | 86 ++--- src/search_cuda.h | 4 +- test/c_api_tests.cpp | 153 ++++++++- test/csharp/TestOnnxRuntimeGenAIAPI.cs | 165 ++++++++- test/model_tests.cpp | 124 +++---- test/python/_test_utils.py | 31 ++ test/python/conftest.py | 71 ++-- test/python/test_onnxruntime_genai.py | 28 +- test/python/test_onnxruntime_genai_api.py | 51 +-- ..._phi2.py => test_onnxruntime_genai_e2e.py} | 24 +- test/sampling_benchmark.cpp | 174 +++++----- test/sampling_tests.cpp | 325 ++++++++++-------- 49 files changed, 1561 insertions(+), 874 deletions(-) delete mode 100644 .pipelines/stages/jobs/nuget-linux-packaging-job.yml create mode 100644 .pipelines/stages/jobs/nuget-packaging-job.yml delete mode 100644 .pipelines/stages/jobs/nuget-win-packaging-job.yml create mode 100644 .pipelines/stages/jobs/steps/compliant/esrp_nuget.yml create mode 100644 .pipelines/stages/jobs/steps/nuget-releasing-step.yml create mode 100644 .pipelines/stages/jobs/steps/utils/get-nuget-package-version-as-variable.yml rename test/python/{test_onnxruntime_genai_phi2.py => test_onnxruntime_genai_e2e.py} (65%) diff --git a/.github/workflows/linux-cpu-x64-build.yml b/.github/workflows/linux-cpu-x64-build.yml index 1af392e22..fe5c92ad5 100644 --- a/.github/workflows/linux-cpu-x64-build.yml +++ b/.github/workflows/linux-cpu-x64-build.yml @@ -49,6 +49,8 @@ jobs: echo "::add-mask::$HF_TOKEN" echo "HF_TOKEN=$HF_TOKEN" >> $GITHUB_ENV + # This will also download all the test models to the test/test_models directory + # These models are used by the python tests as well as C#, C++ and others. - name: Run the python tests run: | python3 test/python/test_onnxruntime_genai.py --cwd test/python --test_models test/test_models diff --git a/.github/workflows/win-gpu-x64-build.yml b/.github/workflows/win-gpu-x64-build.yml index 60768a3b8..48afb21d4 100644 --- a/.github/workflows/win-gpu-x64-build.yml +++ b/.github/workflows/win-gpu-x64-build.yml @@ -52,6 +52,10 @@ jobs: cmake --preset windows_x64_cuda_release -T cuda=${{ env.cuda_dir }}\\v${{ env.cuda_version }} -DTEST_PHI2=False cmake --build --preset windows_x64_cuda_release --parallel + - name: Add CUDA to PATH + run: | + echo "${{ env.cuda_dir }}\\v${{ env.cuda_version }}\\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Install the Python Wheel and Test Dependencies run: | python -m pip install (Get-ChildItem ("$env:cmake_build_dir\wheel\*.whl")) @@ -68,10 +72,6 @@ jobs: run: | python test/python/test_onnxruntime_genai.py --cwd "test\python" --test_models "test\test_models" - - name: Add CUDA to PATH - run: | - echo "${{ env.cuda_dir }}\\v${{ env.cuda_version }}\\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - - name: Build the C# API and Run the C# Tests run: | cd test\csharp diff --git a/.pipelines/nuget-publishing.yml b/.pipelines/nuget-publishing.yml index bb639be7c..e91b57489 100644 --- a/.pipelines/nuget-publishing.yml +++ b/.pipelines/nuget-publishing.yml @@ -33,6 +33,16 @@ parameters: - '12.2' default: '11.8' +- name: publish_to_ado_feed + displayName: 'Publish to Azure DevOps Feed' + type: boolean + default: false + +- name: publish_to_nuget + displayName: 'Publish to NuGet.org' + type: boolean + default: false + resources: repositories: - repository: manylinux @@ -51,4 +61,5 @@ stages: enable_linux_cuda: ${{ parameters.enable_linux_cuda }} ort_version: ${{ parameters.ort_version }} cuda_version: ${{ parameters.cuda_version }} - + publish_to_nuget: ${{ parameters.publish_to_nuget }} + publish_to_ado_feed: ${{ parameters.publish_to_ado_feed }} \ No newline at end of file diff --git a/.pipelines/stages/jobs/nuget-linux-packaging-job.yml b/.pipelines/stages/jobs/nuget-linux-packaging-job.yml deleted file mode 100644 index 081e8ef97..000000000 --- a/.pipelines/stages/jobs/nuget-linux-packaging-job.yml +++ /dev/null @@ -1,51 +0,0 @@ -parameters: -- name: arch - type: string -- name: ep - type: string -- name: ort_version - type: string -- name: cuda_version - type: string - default: '' - -jobs: -- job: Linux_Nuget_Packaging_${{ parameters.ep }}_${{ parameters.arch }} - pool: 'onnxruntime-Ubuntu2204-AMD-CPU' - timeoutInMinutes: 180 - variables: - - name: artifactName - value: 'onnxruntime-genai-capi-linux-${{ parameters.ep }}-${{ parameters.arch }}' - - name: ort_version - value: ${{ parameters.ort_version }} - - name: arch - value: ${{ parameters.arch }} - - name: ep - value: ${{ parameters.ep }} - - name: buildDir - value: 'build/gcc_${{ parameters.ep }}/release' - - name: ort_filename - ${{ if eq(parameters.ep, 'cpu') }}: - value: 'onnxruntime-linux-${{ parameters.arch }}-${{ parameters.ort_version }}' - ${{ else}}: - ${{if eq(parameters.cuda_version, '11.8') }}: - value: 'onnxruntime-linux-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' - ${{ else }}: - value: 'onnxruntime-linux-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' - workspace: - clean: all - steps: - - template: steps/capi-linux-step.yml - parameters: - target: 'onnxruntime-genai' - genai_src: '$(Build.SourcesDirectory)/onnxruntime-genai' - -# TODO: Add a step to build the nuget package - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai capi' - inputs: - ArtifactName: $(artifactName) - - - template: steps/compliant-and-cleanup-step.yml - diff --git a/.pipelines/stages/jobs/nuget-packaging-job.yml b/.pipelines/stages/jobs/nuget-packaging-job.yml new file mode 100644 index 000000000..af5250c3c --- /dev/null +++ b/.pipelines/stages/jobs/nuget-packaging-job.yml @@ -0,0 +1,77 @@ +parameters: +- name: arch + type: string +- name: ep + type: string +- name: ort_version + type: string +- name: cuda_version + type: string + default: '' +- name: os + type: string +- name: publish_to_ado_feed + type: boolean +- name: publish_to_nuget + type: boolean +jobs: +- job: nuget_${{ parameters.os }}_${{ parameters.ep }}_${{ parameters.arch }}_packaging + ${{ if eq(parameters.os, 'linux') }}: + pool: 'onnxruntime-Ubuntu2204-AMD-CPU' + ${{ if eq(parameters.os, 'win') }}: + pool: 'onnxruntime-Win-CPU-2022' + timeoutInMinutes: 180 +# set variables here to be used in the template and steps + variables: + - name: arch + value: ${{ parameters.arch }} + - name: artifactName + value: 'onnxruntime-genai-${{ parameters.os }}-${{ parameters.ep }}-${{ parameters.arch }}' + - name: buildConfig + value: 'Release' + - name: buildDir + value: 'build/${{ parameters.ep }}' + - name: cuda_version + value: ${{ parameters.cuda_version }} + - name: ep + value: ${{ parameters.ep }} + - name: ort_version + value: ${{ parameters.ort_version }} + - name: GDN_CODESIGN_TARGETDIRECTORY + value: '$(Build.ArtifactStagingDirectory)/nuget' + - name: ort_filename + ${{ if eq(parameters.ep, 'cpu') }}: + value: 'onnxruntime-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.ort_version }}' + ${{ else}}: + ${{if eq(parameters.cuda_version, '11.8') }}: + value: 'onnxruntime-${{ parameters.os }}-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' + ${{ else }}: + value: 'onnxruntime-${{ parameters.os }}-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' + - name: genai_nuget_ext + ${{ if eq(parameters.ep, 'cpu') }}: + value: '' + ${{ if eq(parameters.ep, 'cuda') }}: + value: '.Cuda' + - name: ort_nuget_ext + ${{ if eq(parameters.ep, 'cpu') }}: + value: '' + ${{ if eq(parameters.ep, 'cuda') }}: + value: '.Gpu' + workspace: + clean: all + steps: + - template: steps/capi-${{ parameters.os }}-step.yml + parameters: + target: 'onnxruntime-genai' + +# TODO: Add a step to build the linux nuget package + - ${{ if eq(parameters.os, 'win') }}: + - template: steps/nuget-${{ parameters.os }}-step.yml + - ${{ if or(eq(parameters.publish_to_nuget, true), eq(parameters.publish_to_ado_feed, true))}}: + - template: steps/nuget-releasing-step.yml + parameters: + publish_to_ado_feed: ${{ parameters.publish_to_ado_feed }} + publish_to_nuget: ${{ parameters.publish_to_nuget }} + + - template: steps/compliant-and-cleanup-step.yml + diff --git a/.pipelines/stages/jobs/nuget-win-packaging-job.yml b/.pipelines/stages/jobs/nuget-win-packaging-job.yml deleted file mode 100644 index de370fe18..000000000 --- a/.pipelines/stages/jobs/nuget-win-packaging-job.yml +++ /dev/null @@ -1,70 +0,0 @@ -parameters: -- name: arch - type: string - values: - - 'x64' - - 'arm64' -- name: ep - type: string - values: - - 'cpu' - - 'cuda' -- name: ort_version - type: string -- name: cuda_version - type: string - default: '' -jobs: -- job: Windows_Nuget_Packaging_${{ parameters.ep }}_${{ parameters.arch }} - pool: 'onnxruntime-Win-CPU-2022' - timeoutInMinutes: 180 - variables: - - name: buildConfig - value: 'Release' - - name: cuda_version - value: ${{ parameters.cuda_version }} - - name: ort_version - value: ${{ parameters.ort_version }} - - name: arch - value: ${{ parameters.arch }} - - name: ep - value: ${{ parameters.ep }} - - name: buildDir - value: 'build\release\${{ parameters.ep }}_default' - - name: artifactName - value : 'onnxruntime-genai-capi-win-${{ parameters.ep }}-${{ parameters.arch }}' - - name: ort_filename - ${{ if eq(parameters.ep, 'cpu') }}: - value: 'onnxruntime-win-${{ parameters.arch }}-${{ parameters.ort_version }}' - ${{ else}}: - ${{if eq(parameters.cuda_version, '11.8') }}: - value: 'onnxruntime-win-${{ parameters.arch }}-gpu-${{ parameters.ort_version }}' - ${{ else }}: - value: 'onnxruntime-win-${{ parameters.arch }}-cuda12-${{ parameters.ort_version }}' - - name: genai_nuget_ext - ${{ if eq(parameters.ep, 'cpu') }}: - value: '' - ${{ if eq(parameters.ep, 'cuda') }}: - value: '.Cuda' - - name: ort_nuget_ext - ${{ if eq(parameters.ep, 'cpu') }}: - value: '' - ${{ if eq(parameters.ep, 'cuda') }}: - value: '.Gpu' - workspace: - clean: all - steps: - - template: steps/capi-win-step.yml - parameters: - target: 'onnxruntime-genai' - - - template: steps/nuget-win-step.yml - - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai capi' - inputs: - ArtifactName: $(artifactName) - - - template: steps/compliant-and-cleanup-step.yml - diff --git a/.pipelines/stages/jobs/py-linux-packaging-job.yml b/.pipelines/stages/jobs/py-linux-packaging-job.yml index 5b607b7cf..32ccc858b 100644 --- a/.pipelines/stages/jobs/py-linux-packaging-job.yml +++ b/.pipelines/stages/jobs/py-linux-packaging-job.yml @@ -31,6 +31,7 @@ jobs: workspace: clean: all pool: 'onnxruntime-Ubuntu2204-AMD-CPU' +# set variables here to be used in the template and steps variables: # The build machine pool doesn't have dotnet, so it can't run CG. - name: skipComponentGovernanceDetection @@ -39,6 +40,8 @@ jobs: value: ${{ parameters.arch }} - name: ep value: ${{ parameters.ep }} + - name: artifactName + value: 'onnxruntime-genai-capi-linux-${{ parameters.ep }}-${{ parameters.arch }}-python' - name: cuda_version value: ${{ parameters.cuda_version }} - name: ort_version @@ -58,10 +61,5 @@ jobs: target: 'python' genai_src: '$(Build.SourcesDirectory)/onnxruntime-genai' - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime python wheel' - inputs: - ArtifactName: onnxruntime-genai-linux-$(ep)-$(arch) - - template: steps/compliant-and-cleanup-step.yml diff --git a/.pipelines/stages/jobs/py-win-packaging-job.yml b/.pipelines/stages/jobs/py-win-packaging-job.yml index 4d85511be..0989398eb 100644 --- a/.pipelines/stages/jobs/py-win-packaging-job.yml +++ b/.pipelines/stages/jobs/py-win-packaging-job.yml @@ -24,11 +24,14 @@ jobs: Python312_x64: PythonVersion: '3.12' timeoutInMinutes: 180 +# set variables here to be used in the template and steps variables: - name: ep value: ${{ parameters.ep }} - name: cuda_version value: ${{ parameters.cuda_version }} + - name: artifactName + value: 'onnxruntime-genai-capi-win-${{ parameters.ep }}-${{ parameters.arch }}-wheel' - name: arch value: ${{ parameters.arch }} - name: ort_version @@ -64,32 +67,5 @@ jobs: - template: steps/capi-win-step.yml parameters: target: 'python' -# ep: ${{ parameters.ep }} - - - template: steps/compliant/win-esrp-dll-step.yml - parameters: - FolderPath: '$(Build.SourcesDirectory)\build\release\$(ep)_default\wheel\onnxruntime_genai' - DisplayName: 'ESRP - PYD Sign' - DoEsrp: true - Pattern: '*.pyd' - - - powershell: | - cmake --build --preset windows_$(arch)_$(ep)_release --parallel --PyPackageBuild - displayName: 'Build Python Wheel' - - - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse - - - task: CopyFiles@2 - displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\build\release\$(ep)_default\wheel' - Contents: '*.whl' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: ONNXRuntime Genai python wheel' - inputs: - ArtifactName: onnxruntime-genai-win-$(ep)-$(arch) - template: steps/compliant-and-cleanup-step.yml \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/capi-linux-step.yml b/.pipelines/stages/jobs/steps/capi-linux-step.yml index 2007ef670..6fa0f3c92 100644 --- a/.pipelines/stages/jobs/steps/capi-linux-step.yml +++ b/.pipelines/stages/jobs/steps/capi-linux-step.yml @@ -1,30 +1,35 @@ parameters: -- name: genai_src - type: string - name: target type: string steps: -- checkout: self # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/onnxruntime-genai, +- checkout: self clean: true + path: onnxruntime-genai submodules: recursive -- checkout: manylinux # due to checkout multiple repos, the root directory is $(Build.SourcesDirectory)/manylinux, +- checkout: manylinux clean: true + path: onnxruntime-genai/manylinux submodules: recursive +- script: | + set -e -x + echo "$(Build.SourcesDirectory)" + echo "$(Build.Repository.LocalPath)" + ls $(Build.SourcesDirectory) -R + displayName: 'List files from SourceDirectory' + - template: utils/set-nightly-build-option-variable.yml - bash: | echo "arch=$(arch)" echo "ep=$(ep)" - echo "genai_src=${{ parameters.genai_src }}" displayName: 'Print Parameters' - template: utils/download-ort.yml parameters: archiveType: 'tgz' - genai_src: ${{ parameters.genai_src }} - bash: | set -e -x az login --identity --username 63b63039-6328-442f-954b-5a64d124e5b4 @@ -33,18 +38,18 @@ steps: --context tools/ci_build/github/linux/docker/manylinux \ --docker-build-args "--build-arg BUILD_UID=$( id -u )" \ --container-registry onnxruntimebuildcache \ - --manylinux-src $(Build.SourcesDirectory)/manylinux \ + --manylinux-src manylinux \ --multiple_repos \ --repository onnxruntime$(ep)build$(arch) displayName: 'Get Docker Image' - workingDirectory: '${{ parameters.genai_src }}' + workingDirectory: '$(Build.Repository.LocalPath)' - ${{ if eq(parameters.target, 'onnxruntime-genai') }}: - script: | set -e -x docker run \ --rm \ - --volume ${{ parameters.genai_src }}:/ort_genai_src \ + --volume $(Build.Repository.LocalPath):/ort_genai_src \ -w /ort_genai_src/ onnxruntime$(ep)build$(arch) \ bash -c " \ /usr/bin/cmake --preset linux_gcc_$(ep)_release \ @@ -52,22 +57,28 @@ steps: /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ --target onnxruntime-genai" displayName: 'Build GenAi' - workingDirectory: '${{ parameters.genai_src }}' + workingDirectory: '$(Build.Repository.LocalPath)' - task: BinSkim@4 displayName: 'Run BinSkim' inputs: - AnalyzeTargetGlob: '$(Build.SourcesDirectory)/onnxruntime-genai/build/**/*genai.so' + AnalyzeTargetGlob: '$(Build.Repository.LocalPath)/build/**/*genai.so' continueOnError: true - template: utils/capi-archive.yml parameters: - genai_src: ${{ parameters.genai_src }} archiveType: tar + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime Genai capi' + inputs: + ArtifactName: $(artifactName)-capi + PathtoPublish: '$(Build.ArtifactStagingDirectory)/capi' + - ${{ if eq(parameters.target, 'python') }}: - bash: | set -e -x docker run \ --rm \ - --volume ${{ parameters.genai_src }}:/ort_genai_src \ + --volume $(Build.Repository.LocalPath):/ort_genai_src \ -w /ort_genai_src/ onnxruntime$(ep)build$(arch) \ bash -c " \ /usr/bin/cmake --preset linux_gcc_$(ep)_release \ @@ -77,12 +88,19 @@ steps: /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ --target python" displayName: 'Build Python $(PyNoDotVer)' - workingDirectory: '${{ parameters.genai_src }}' + workingDirectory: '$(Build.Repository.LocalPath)' + + - task: BinSkim@4 + displayName: 'Run BinSkim' + inputs: + AnalyzeTargetGlob: '$(Build.Repository.LocalPath)/**/*.pyd' + continueOnError: true + - bash: | set -e -x docker run \ --rm \ - --volume ${{ parameters.genai_src }}:/ort_genai_src \ + --volume $(Build.Repository.LocalPath):/ort_genai_src \ -w /ort_genai_src/ onnxruntime$(ep)build$(arch) \ bash -c " \ /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ @@ -92,15 +110,22 @@ steps: /usr/bin/cmake --build --preset linux_gcc_$(ep)_release \ --target PyPackageBuild" displayName: 'PyPackageBuild $(PyNoDotVer)' - workingDirectory: '${{ parameters.genai_src }}' + workingDirectory: '$(Build.Repository.LocalPath)' + - task: CopyFiles@2 displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' inputs: - SourceFolder: '${{ parameters.genai_src }}/build/gcc_$(ep)/release/wheel' + SourceFolder: '$(Build.Repository.LocalPath)/build/$(ep)/wheel' Contents: '*manylinux*.whl' - TargetFolder: '$(Build.ArtifactStagingDirectory)' + TargetFolder: '$(Build.ArtifactStagingDirectory)/wheel' + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime python wheel' + inputs: + ArtifactName: $(artifactName) + PathtoPublish: '$(Build.ArtifactStagingDirectory)/wheel' - script: | - ls ${{ parameters.genai_src }} -R + ls $(Build.Repository.LocalPath) -R displayName: 'List files from SourceDirectory' diff --git a/.pipelines/stages/jobs/steps/capi-win-step.yml b/.pipelines/stages/jobs/steps/capi-win-step.yml index ac6baedbe..b28230a7e 100644 --- a/.pipelines/stages/jobs/steps/capi-win-step.yml +++ b/.pipelines/stages/jobs/steps/capi-win-step.yml @@ -26,7 +26,6 @@ steps: echo "ep=$(ep)" echo "cuda_version=$(cuda_version)" echo "target=${{ parameters.target }}" - echo "ort_filename=$(ort_filename)" displayName: 'Print Parameters' - template: utils/download-ort.yml @@ -62,6 +61,7 @@ steps: parameters: FolderPath: '$(buildDir)' DisplayName: 'ESRP - Sign C++ dlls' + Pattern: '*genai.dll' - task: BinSkim@4 displayName: 'Run BinSkim' @@ -74,9 +74,43 @@ steps: genai_src: '$(Build.SourcesDirectory)' archiveType: zip + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime Genai capi' + inputs: + ArtifactName: $(artifactName)-capi + PathtoPublish: '$(Build.ArtifactStagingDirectory)/capi' + - ${{ if eq(parameters.target, 'python') }}: - task: BinSkim@4 displayName: 'Run BinSkim' inputs: AnalyzeTargetGlob: '$(Build.SourcesDirectory)\**\*.pyd' continueOnError: true + + - template: compliant/win-esrp-dll-step.yml + parameters: + FolderPath: '$(Build.Repository.LocalPath)\build\$(ep)\wheel\onnxruntime_genai' + DisplayName: 'ESRP - PYD Sign' + DoEsrp: true + Pattern: '*.pyd' + + - powershell: | + cmake --build --preset windows_$(arch)_$(ep)_release --parallel --PyPackageBuild + displayName: 'Build Python Wheel' + + - powershell: | + Get-ChildItem -Path $(Build.Repository.LocalPath) -Recurse + + - task: CopyFiles@2 + displayName: 'Copy Python Wheel to: $(Build.ArtifactStagingDirectory)' + inputs: + SourceFolder: '$(Build.Repository.LocalPath)\build\$(ep)\wheel' + Contents: '*.whl' + TargetFolder: '$(Build.ArtifactStagingDirectory)\wheel' + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime python wheel' + inputs: + ArtifactName: $(artifactName)-wheel + PathtoPublish: '$(Build.ArtifactStagingDirectory)\wheel' + diff --git a/.pipelines/stages/jobs/steps/compliant/esrp_nuget.yml b/.pipelines/stages/jobs/steps/compliant/esrp_nuget.yml new file mode 100644 index 000000000..081e7a809 --- /dev/null +++ b/.pipelines/stages/jobs/steps/compliant/esrp_nuget.yml @@ -0,0 +1,31 @@ +parameters: + FolderPath: '' + DisplayName: '' + DoEsrp: 'false' + +steps: +- ${{ if eq(parameters['DoEsrp'], 'true') }}: + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@2 + displayName: ${{ parameters.DisplayName }} + inputs: + ConnectedServiceName: 'OnnxRuntime CodeSign 20190817' + FolderPath: ${{ parameters.FolderPath }} + Pattern: '*.nupkg' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetVerify", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/nuget-releasing-step.yml b/.pipelines/stages/jobs/steps/nuget-releasing-step.yml new file mode 100644 index 000000000..8442fd069 --- /dev/null +++ b/.pipelines/stages/jobs/steps/nuget-releasing-step.yml @@ -0,0 +1,49 @@ +parameters: +- name: publish_to_ado_feed + type: boolean +- name: publish_to_nuget + type: boolean +steps: +- task: NuGetToolInstaller@1 + inputs: + versionSpec: 6.8.x + +- powershell: | + New-Item -Path $(Agent.TempDirectory) -Name "binfiles" -ItemType "directory" + $base_path_name = Join-Path -Path $(Agent.TempDirectory) -ChildPath "binfiles" + Get-ChildItem $(GDN_CODESIGN_TARGETDIRECTORY) -Filter *.nupkg | + Foreach-Object { + $dir_name = Join-Path -Path $base_path_name -ChildPath $_.Basename + $cmd = "7z.exe x $($_.FullName) -y -o$dir_name" + Write-Output $cmd + Invoke-Expression -Command $cmd + } + dir $(Agent.TempDirectory) + tree $(Agent.TempDirectory) + workingDirectory: '$(Agent.TempDirectory)' + +- task: CodeSign@1 + displayName: 'Run Codesign Validation' + +- task: PublishSecurityAnalysisLogs@3 + displayName: 'Publish Security Analysis Logs' + continueOnError: true + +- task: PostAnalysis@2 + inputs: + GdnBreakAllTools: true + GdnBreakPolicy: M365 + GdnBreakPolicyMinSev: Error + +- template: utils/get-nuget-package-version-as-variable.yml + parameters: + packageFolder: '$(GDN_CODESIGN_TARGETDIRECTORY)' +#This task must be run on a Windows machine +- ${{ if eq(parameters.publish_to_ado_feed, true) }}: + - task: NuGetCommand@2 + displayName: 'NuGet push to Azure DevOps Feed' + inputs: + command: push + packagesToPush: '$(GDN_CODESIGN_TARGETDIRECTORY)/*.nupkg' + publishVstsFeed: 'PublicPackages/onnxruntime-genai' + allowPackageConflicts: true \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/nuget-win-step.yml b/.pipelines/stages/jobs/steps/nuget-win-step.yml index 6d63c3e60..214635822 100644 --- a/.pipelines/stages/jobs/steps/nuget-win-step.yml +++ b/.pipelines/stages/jobs/steps/nuget-win-step.yml @@ -14,9 +14,9 @@ steps: parameters: FolderPath: '$(Build.SourcesDirectory)\src\csharp\bin\Release\' DisplayName: 'ESRP - Sign C# dlls' - + Pattern: '*OnnxRuntimeGenAI*.dll' - powershell: | - $VERSION = '0.1.0-rc3' + $VERSION = '0.1.0-rc4' nuget.exe pack Microsoft.ML.OnnxRuntimeGenAI.nuspec ` -Prop version=$VERSION ` -Prop genai_nuget_ext=$(genai_nuget_ext) ` @@ -41,4 +41,16 @@ steps: inputs: SourceFolder: '$(Build.SourcesDirectory)\nuget' Contents: '*.nupkg' - TargetFolder: '$(Build.ArtifactStagingDirectory)' \ No newline at end of file + TargetFolder: '$(Build.ArtifactStagingDirectory)\nuget' + +- template: compliant/esrp_nuget.yml + parameters: + DisplayName: 'ESRP - sign NuGet package' + FolderPath: '$(Build.ArtifactStagingDirectory)\nuget' + DoEsrp: 'true' + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact: ONNXRuntime Genai capi' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\nuget' + ArtifactName: $(artifactName)-nuget' \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/utils/capi-archive.yml b/.pipelines/stages/jobs/steps/utils/capi-archive.yml index 3c872a85d..e9c62a9af 100644 --- a/.pipelines/stages/jobs/steps/utils/capi-archive.yml +++ b/.pipelines/stages/jobs/steps/utils/capi-archive.yml @@ -7,7 +7,7 @@ steps: - bash: | echo "##[error]Error: artifactName and buildDir are not set" exit 1 - displayName: 'Check if variables ort_filename and ort_filename are set' + displayName: 'Check if variables artifactName and buildDir are set' condition: or( eq (variables['artifactName'], ''), eq (variables['buildDir'], '')) - task: CopyFiles@2 @@ -62,9 +62,9 @@ steps: archiveType: ${{ parameters.archiveType }} ${{ if eq(parameters.archiveType, 'tar') }}: tarCompression: 'gz' - archiveFile: '$(Build.ArtifactStagingDirectory)/$(artifactName).tgz' + archiveFile: '$(Build.ArtifactStagingDirectory)/capi/$(artifactName).tgz' ${{ else }}: - archiveFile: '$(Build.ArtifactStagingDirectory)/$(artifactName).zip' + archiveFile: '$(Build.ArtifactStagingDirectory)/capi/$(artifactName).zip' replaceExistingArchive: true - task: DeleteFiles@1 diff --git a/.pipelines/stages/jobs/steps/utils/download-ort.yml b/.pipelines/stages/jobs/steps/utils/download-ort.yml index f9b5001d8..aa22b3974 100644 --- a/.pipelines/stages/jobs/steps/utils/download-ort.yml +++ b/.pipelines/stages/jobs/steps/utils/download-ort.yml @@ -7,7 +7,7 @@ steps: - bash: | echo "##[error]Error: ort_version and ort_filename are not set" exit 1 - displayName: 'Check if variables ort_filename and ort_filename are set' + displayName: 'Check if variables ort_version and ort_filename are set' condition: or( eq (variables['ort_version'], ''), eq (variables['ort_filename'], '')) - task: DownloadGitHubRelease@0 diff --git a/.pipelines/stages/jobs/steps/utils/get-nuget-package-version-as-variable.yml b/.pipelines/stages/jobs/steps/utils/get-nuget-package-version-as-variable.yml new file mode 100644 index 000000000..4edf0d03a --- /dev/null +++ b/.pipelines/stages/jobs/steps/utils/get-nuget-package-version-as-variable.yml @@ -0,0 +1,42 @@ +parameters: + packageFolder: $(Build.ArtifactStagingDirectory) + +steps: +- task: CmdLine@2 + condition: eq(variables['Agent.OS'], 'Windows_NT') + displayName: 'Extract version number from the NuPkg file, Windows VMs' + inputs: + workingDirectory: '${{ parameters.packageFolder }}' + script: | + SETLOCAL EnableDelayedExpansion + FOR /R %%i IN (Microsoft.ML.OnnxRuntime.Managed*.nupkg) do ( + set filename=%%~ni + set ortversion=!filename:~33! + @echo ortversion is !ortversion! + @echo ##vso[task.setvariable variable=NuGetPackageVersionNumber;]!ortversion! + ) +- task: CmdLine@2 + condition: eq(variables['Agent.OS'], 'Windows_NT') + displayName: 'Extract version number from the DirectML NuPkg file, Windows VMs' + inputs: + workingDirectory: '${{ parameters.packageFolder }}' + script: | + SETLOCAL EnableDelayedExpansion + FOR /R %%i IN (Microsoft.ML.OnnxRuntime.DirectML*.nupkg) do ( + set filename=%%~ni + set ortversion=!filename:~34! + @echo DirectMLNuGetPackageVersionNumber is !ortversion! + @echo ##vso[task.setvariable variable=DirectMLNuGetPackageVersionNumber;]!ortversion! + ) +- task: CmdLine@2 + condition: not(eq(variables['Agent.OS'], 'Windows_NT')) + displayName: 'Extract version number from the NuPkg file, Unix VMs' + inputs: + workingDirectory: '${{ parameters.packageFolder }}' + script: | + filenamewithext=$(ls Microsoft.ML.OnnxRuntime.Managed*nupkg) + filename=${filenamewithext%.*} + ortversion=${filename:33} + # Do not output ##vso[] commands with `set -x` or they may be parsed again and include a trailing quote. + set +x + echo "##vso[task.setvariable variable=NuGetPackageVersionNumber;]$ortversion" diff --git a/.pipelines/stages/nuget-packaging-stage.yml b/.pipelines/stages/nuget-packaging-stage.yml index e1125cf96..db500916b 100644 --- a/.pipelines/stages/nuget-packaging-stage.yml +++ b/.pipelines/stages/nuget-packaging-stage.yml @@ -12,35 +12,49 @@ parameters: - name: cuda_version type: string default: '' - +- name: publish_to_ado_feed + type: boolean +- name: publish_to_nuget + type: boolean stages: - stage: nuget_packaging jobs: - ${{ if eq(parameters.enable_win_cpu, true) }}: - - template: jobs/nuget-win-packaging-job.yml + - template: jobs/nuget-packaging-job.yml parameters: arch: 'x64' ep: 'cpu' ort_version: ${{ parameters.ort_version }} + os: 'win' + publish_to_ado_feed: ${{ parameters.publish_to_ado_feed }} + publish_to_nuget: ${{ parameters.publish_to_nuget }} - ${{ if eq(parameters.enable_win_cuda, true) }}: - - template: jobs/nuget-win-packaging-job.yml + - template: jobs/nuget-packaging-job.yml parameters: arch: 'x64' cuda_version: ${{ parameters.cuda_version }} ep: 'cuda' ort_version: ${{ parameters.ort_version }} - + os: 'win' + publish_to_ado_feed: ${{ parameters.publish_to_ado_feed }} + publish_to_nuget: ${{ parameters.publish_to_nuget }} - ${{ if eq(parameters.enable_linux_cpu, true) }}: - - template: jobs/nuget-linux-packaging-job.yml + - template: jobs/nuget-packaging-job.yml parameters: arch: 'x64' ep: 'cpu' ort_version: ${{ parameters.ort_version }} + os: 'linux' + publish_to_ado_feed: ${{ parameters.publish_to_ado_feed }} + publish_to_nuget: ${{ parameters.publish_to_nuget }} - ${{ if eq(parameters.enable_linux_cuda, true) }}: - - template: jobs/nuget-linux-packaging-job.yml + - template: jobs/nuget-packaging-job.yml parameters: arch: 'x64' cuda_version: ${{ parameters.cuda_version }} ep: 'cuda' - ort_version: ${{ parameters.ort_version }} \ No newline at end of file + ort_version: ${{ parameters.ort_version }} + os: 'linux' + publish_to_ado_feed: ${{ parameters.publish_to_ado_feed }} + publish_to_nuget: ${{ parameters.publish_to_nuget }} \ No newline at end of file diff --git a/VERSION_INFO b/VERSION_INFO index 7190fc3db..3e2177af6 100644 --- a/VERSION_INFO +++ b/VERSION_INFO @@ -1 +1 @@ -0.1.0rc3 \ No newline at end of file +0.1.0rc4 \ No newline at end of file diff --git a/src/config.h b/src/config.h index 6cc634658..2621edc21 100644 --- a/src/config.h +++ b/src/config.h @@ -79,7 +79,7 @@ struct Config { int num_return_sequences{1}; float repetition_penalty{1.0f}; // 1.0 means no penalty. int top_k{}; // Number of highest probability vocabulary tokens to keep for top-k-filtering that will be used by default in the generate method of the model. - float top_p{1.0f}; // If set to float < 1, only the most probable tokens with probabilities that add up to top_p or higher are kept for generation. + float top_p{}; // If set to float >0 and <1, only the most probable tokens with probabilities that add up to top_p or higher are kept for generation. float temperature{1.0f}; bool early_stopping{true}; // Whether to stop the beam search when at least num_beams sentences are finished per batch or not. int no_repeat_ngram_size{}; diff --git a/src/csharp/Generator.cs b/src/csharp/Generator.cs index 1dc81883b..64c1c5623 100644 --- a/src/csharp/Generator.cs +++ b/src/csharp/Generator.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Runtime.InteropServices; namespace Microsoft.ML.OnnxRuntimeGenAI { @@ -26,9 +25,9 @@ public void ComputeLogits() Result.VerifySuccess(NativeMethods.OgaGenerator_ComputeLogits(_generatorHandle)); } - public void GenerateNextTokenTop() + public void GenerateNextToken() { - Result.VerifySuccess(NativeMethods.OgaGenerator_GenerateNextToken_Top(_generatorHandle)); + Result.VerifySuccess(NativeMethods.OgaGenerator_GenerateNextToken(_generatorHandle)); } public ReadOnlySpan GetSequence(ulong index) diff --git a/src/csharp/NativeMethods.cs b/src/csharp/NativeMethods.cs index 4b41102d7..552c9046a 100644 --- a/src/csharp/NativeMethods.cs +++ b/src/csharp/NativeMethods.cs @@ -78,7 +78,7 @@ internal class NativeLib // This function is used to generate the next token in the sequence using the greedy search algorithm. [DllImport(NativeLib.DllName, CallingConvention = CallingConvention.Winapi)] - public static extern IntPtr /* OgaResult* */ OgaGenerator_GenerateNextToken_Top(IntPtr /* OgaGenerator* */ generator); + public static extern IntPtr /* OgaResult* */ OgaGenerator_GenerateNextToken(IntPtr /* OgaGenerator* */ generator); // This function returns the length of the sequence at the given index. [DllImport(NativeLib.DllName, CallingConvention = CallingConvention.Winapi)] diff --git a/src/generators.cpp b/src/generators.cpp index db27628da..cee8b1b02 100644 --- a/src/generators.cpp +++ b/src/generators.cpp @@ -66,11 +66,17 @@ std::unique_ptr CreateSearch(const GeneratorParams& params) { return std::make_unique(params); } -Generator::Generator(const Model& model, const GeneratorParams& params) : model_{model} { +Generator::Generator(const Model& model, const GeneratorParams& params) : model_{model.shared_from_this()} { if (params.search.max_length == 0) throw std::runtime_error("search max_length is 0"); if (params.search.max_length > model.config_->model.context_length) throw std::runtime_error("max_length cannot be greater than model context_length"); + if (params.batch_size < 1) + throw std::runtime_error("batch_size must be 1 or greater"); + if (params.vocab_size < 1) + throw std::runtime_error("vocab_size must be 1 or greater"); + if (params.sequence_length >= params.search.max_length) + throw std::runtime_error("input sequence_length is >= max_length"); search_ = CreateSearch(params); state_ = model.CreateState(search_->GetSequenceLengths(), params); @@ -78,12 +84,12 @@ Generator::Generator(const Model& model, const GeneratorParams& params) : model_ void Generator::ComputeLogits() { if (computed_logits_) - throw std::runtime_error("ComputeLogits called again without calling GenerateNextToken* first"); + throw std::runtime_error("ComputeLogits called again without calling GenerateNextToken first"); search_->SetLogits(state_->Run(search_->GetSequenceLength(), search_->GetNextTokens(), search_->GetNextIndices())); computed_logits_ = true; - auto& search = search_->params_.search; + auto& search = search_->params_->search; search_->ApplyMinLength(search.min_length); search_->ApplyRepetitionPenalty(search.repetition_penalty); } @@ -95,46 +101,37 @@ bool Generator::IsDone() const { return search_->IsDone(); } -void Generator::GenerateNextToken_TopK_TopP(int top_k, float top_p, float temperature) { +void Generator::GenerateNextToken() { if (!computed_logits_) - throw std::runtime_error("Must call ComputeLogits before GenerateNextToken*"); + throw std::runtime_error("Must call ComputeLogits before GenerateNextToken"); computed_logits_ = false; - if (top_k == 1) { + auto& search = search_->params_->search; + if (!search.do_sample || search.top_k == 1) { search_->SelectTop(); return; } // The user explicitly called TopK_TopP on a beam search - if (search_->params_.search.num_beams != 1) + if (search.num_beams != 1) throw std::runtime_error("TopK and TopP cannot be used with a beam search"); // Sanity checks - if (top_p < 0.0f || top_p > 1.0f) + if (search.top_p < 0.0f || search.top_p > 1.0f) throw std::runtime_error("top_p must be between 0.0 and 1.0"); - if (top_k < 0) + if (search.top_k < 0) throw std::runtime_error("top_k must be 0 or greater"); - if (top_p > 0.0f && top_k > 1) { - search_->SampleTopPAndK(top_p, top_k, temperature); - } else if (top_k > 1) { - search_->SampleTopK(top_k, temperature); + if (search.top_p > 0.0f && search.top_p < 1.0f && search.top_k > 1) { + search_->SampleTopKTopP(search.top_k, search.top_p, search.temperature); + } else if (search.top_k > 1) { + search_->SampleTopK(search.top_k, search.temperature); } else { - assert(top_k == 0); - if (top_p == 0.0f) - throw std::runtime_error("top_k and top_p cannot both be zero"); - search_->SampleTopP(top_p, temperature); + assert(search.top_k == 0); + search_->SampleTopP(search.top_p, search.temperature); } } -void Generator::GenerateNextToken() { - auto& search = search_->params_.search; - if (search.do_sample) - GenerateNextToken_TopK_TopP(search.top_k, search.top_p, search.temperature); - else - GenerateNextToken_Top(); -} - RoamingArray Generator::GetSequence(int index) const { return search_->GetSequence(index); } diff --git a/src/generators.h b/src/generators.h index af98aea44..6f69ccb18 100644 --- a/src/generators.h +++ b/src/generators.h @@ -44,7 +44,7 @@ enum struct DeviceType { CUDA, }; -struct GeneratorParams { +struct GeneratorParams : std::enable_shared_from_this { GeneratorParams() = default; // This constructor is only used if doing a custom model handler vs built-in GeneratorParams(const Model& model); @@ -91,6 +91,8 @@ struct GeneratorParams { std::variant inputs; std::vector input_ids_owner; // Backing memory of input_ids in some cases + + std::shared_ptr external_owner_; // Set to 'this' when created by the C API to preserve lifetime }; struct Generator { @@ -98,21 +100,19 @@ struct Generator { bool IsDone() const; void ComputeLogits(); - void GenerateNextToken_TopK_TopP(int top_k, float top_p, float temperature); - void GenerateNextToken_TopP(float p, float temperature) { GenerateNextToken_TopK_TopP(0, p, temperature); } - void GenerateNextToken_TopK(int k, float temperature) { GenerateNextToken_TopK_TopP(k, 0.0f, temperature); } - void GenerateNextToken_Top() { GenerateNextToken_TopK_TopP(1, 0.0f, 0.0f); } void GenerateNextToken(); RoamingArray GetSequence(int index) const; - const Model& model_; + std::shared_ptr model_; std::unique_ptr state_; std::unique_ptr search_; bool computed_logits_{}; // Set to true in ComputeLogits() and false after appending a token to ensure a 1 to 1 call ratio }; -std::unique_ptr CreateModel(OrtEnv& ort_env, const char* config_path); +std::shared_ptr CreateModel(OrtEnv& ort_env, const char* config_path); +std::shared_ptr CreateGeneratorParams(const Model& model); +std::shared_ptr CreateGeneratorParams(); // For benchmarking purposes only std::unique_ptr CreateGenerator(const Model& model, const GeneratorParams& params); std::vector> Generate(const Model& model, const GeneratorParams& params); // Uses CreateGenerator and a simple loop to return the entire sequence diff --git a/src/models/input_ids.cpp b/src/models/input_ids.cpp index 96a8facc8..88d2514b5 100644 --- a/src/models/input_ids.cpp +++ b/src/models/input_ids.cpp @@ -9,24 +9,24 @@ InputIDs::InputIDs(const Model& model, State& state) : model_{model}, state_{state} { name_ = model_.config_->model.decoder.inputs.input_ids.c_str(); - shape_ = {state_.params_.batch_size, state_.params_.sequence_length}; + shape_ = {state_.params_->batch_size, state_.params_->sequence_length}; type_ = model_.session_info_->GetInputDataType(name_); // If 64-bit, convert from 32-bit to 64-bit if (type_ == Ort::TypeToTensorType::type) { value_ = OrtValue::CreateTensor(model.allocator_cpu_, shape_, type_); auto* p_data = value_->GetTensorMutableData(); - for (auto v : state_.params_.input_ids) { + for (auto v : state_.params_->input_ids) { *p_data++ = v; } } else { if (type_ != Ort::TypeToTensorType::type) throw std::runtime_error("InputIDs must be int64 or int32"); - value_ = OrtValue::CreateTensor(model.allocator_cpu_.GetInfo(), std::span(const_cast(state_.params_.input_ids.data()), shape_[0] * shape_[1]), shape_); + value_ = OrtValue::CreateTensor(model.allocator_cpu_.GetInfo(), std::span(const_cast(state_.params_->input_ids.data()), shape_[0] * shape_[1]), shape_); } - value_ = model_.ExpandInputs(value_, state_.params_.search.num_beams); - shape_[0] *= state_.params_.search.num_beams; + value_ = model_.ExpandInputs(value_, state_.params_->search.num_beams); + shape_[0] *= state_.params_->search.num_beams; } void InputIDs::Add() { diff --git a/src/models/kv_cache.cpp b/src/models/kv_cache.cpp index 4a0910066..17515355f 100644 --- a/src/models/kv_cache.cpp +++ b/src/models/kv_cache.cpp @@ -8,7 +8,7 @@ KV_Cache_Combined::KV_Cache_Combined(const Model& model, State& state) : model_{model}, state_{state}, layer_count_{model.config_->model.decoder.num_hidden_layers}, - shape_{2, state_.params_.BatchBeamSize(), model.config_->model.decoder.num_key_value_heads, 0, model.config_->model.decoder.head_size} { + shape_{2, state_.params_->BatchBeamSize(), model.config_->model.decoder.num_key_value_heads, 0, model.config_->model.decoder.head_size} { pasts_.resize(layer_count_); presents_.reserve(layer_count_); @@ -25,7 +25,7 @@ KV_Cache_Combined::KV_Cache_Combined(const Model& model, State& state) type_ = model_.session_info_->GetInputDataType(input_name_strings_[0]); empty_past_ = OrtValue::CreateTensor(*model_.allocator_device_, shape_, type_); - shape_[3] = state_.params_.sequence_length; + shape_[3] = state_.params_->sequence_length; for (int i = 0; i < layer_count_; ++i) { presents_.push_back(OrtValue::CreateTensor(*model.allocator_device_, shape_, type_)); @@ -45,7 +45,7 @@ void KV_Cache_Combined::Add() { } void KV_Cache_Combined::Update(std::span beam_indices, int current_length) { - assert(state_.params_.search.num_beams == 1 || !beam_indices.empty()); // We require beam_indices if we're a beam search + assert(state_.params_->search.num_beams == 1 || !beam_indices.empty()); // We require beam_indices if we're a beam search for (int i = 0; i < layer_count_; i++) { if (beam_indices.empty()) { @@ -117,8 +117,8 @@ KV_Cache::KV_Cache(const Model& model, State& state) : model_{model}, state_{state}, layer_count_{model_.config_->model.decoder.num_hidden_layers}, - past_present_share_buffer_{state_.params_.search.past_present_share_buffer && state_.params_.search.num_beams == 1 && model_.device_type_ == DeviceType::CUDA}, - shape_{state_.params_.BatchBeamSize(), model.config_->model.decoder.num_key_value_heads, 0, model.config_->model.decoder.head_size} { + past_present_share_buffer_{state_.params_->search.past_present_share_buffer && state_.params_->search.num_beams == 1 && model_.device_type_ == DeviceType::CUDA}, + shape_{state_.params_->BatchBeamSize(), model.config_->model.decoder.num_key_value_heads, 0, model.config_->model.decoder.head_size} { pasts_.resize(layer_count_ * 2); presents_.reserve(layer_count_ * 2); @@ -142,9 +142,9 @@ KV_Cache::KV_Cache(const Model& model, State& state) // Set the size after empty_past_ has been created with 0 for this field if (past_present_share_buffer_) - shape_[2] = state_.params_.search.max_length; + shape_[2] = state_.params_->search.max_length; else - shape_[2] = state_.params_.sequence_length; + shape_[2] = state_.params_->sequence_length; for (int i = 0; i < layer_count_; ++i) { presents_.push_back(OrtValue::CreateTensor(*model_.allocator_device_, shape_, type_)); @@ -245,7 +245,7 @@ Cross_Cache::Cross_Cache(const Model& model, State& state) : model_{model}, state_{state}, layer_count_{model_.config_->model.decoder.num_hidden_layers}, - shape_{state_.params_.BatchBeamSize(), model.config_->model.decoder.num_key_value_heads, 1500, model.config_->model.decoder.head_size} { + shape_{state_.params_->BatchBeamSize(), model.config_->model.decoder.num_key_value_heads, 1500, model.config_->model.decoder.head_size} { values_.reserve(layer_count_ * 2); for (int i = 0; i < layer_count_; ++i) { diff --git a/src/models/logits.cpp b/src/models/logits.cpp index 7dc79e53a..d7dd837f3 100644 --- a/src/models/logits.cpp +++ b/src/models/logits.cpp @@ -7,7 +7,7 @@ namespace Generators { Logits::Logits(const Model& model, State& state) : model_{model}, state_{state}, - shape_{static_cast(state_.params_.batch_size) * state_.params_.search.num_beams, state_.params_.sequence_length, state_.params_.vocab_size}, + shape_{static_cast(state_.params_->batch_size) * state_.params_->search.num_beams, state_.params_->sequence_length, state_.params_->vocab_size}, type_{model_.session_info_->GetOutputDataType(model_.config_->model.decoder.outputs.logits)} { if (model_.device_type_ == DeviceType::CPU && type_ != Ort::TypeToTensorType::type) throw std::runtime_error("Model logits_type can only be float32 on CPU"); @@ -34,7 +34,7 @@ RoamingArray Logits::Get() { if (shape_[1] != 1) { const size_t seq_length = shape_[1]; const size_t vocab_size = shape_[2]; - const size_t num_beams = state_.params_.search.num_beams; + const size_t num_beams = state_.params_->search.num_beams; shape_[1] = 1; auto value_next = OrtValue::CreateTensor(*model_.allocator_device_, shape_); @@ -42,12 +42,12 @@ RoamingArray Logits::Get() { size_t vocab_index = 0; // Simpler math to have this index go up by vocab_size for every logit chunk we process - const auto* input_ids = state_.params_.input_ids.data(); - for (int batch_index = 0; batch_index < state_.params_.batch_size; batch_index++) { + const auto* input_ids = state_.params_->input_ids.data(); + for (int batch_index = 0; batch_index < state_.params_->batch_size; batch_index++) { // Find the first non pad token from the end size_t token_index = seq_length; while (token_index-- > 0) { - if (input_ids[token_index] != state_.params_.pad_token_id) + if (input_ids[token_index] != state_.params_->pad_token_id) break; } @@ -57,7 +57,7 @@ RoamingArray Logits::Get() { auto target = logits_next.subspan(vocab_index, vocab_size); #if USE_CUDA if (model_.device_type_ == DeviceType::CUDA) - CudaCheck() == cudaMemcpyAsync(target.data(), source.data(), source.size_bytes(), cudaMemcpyDeviceToDevice, state_.params_.cuda_stream); + CudaCheck() == cudaMemcpyAsync(target.data(), source.data(), source.size_bytes(), cudaMemcpyDeviceToDevice, state_.params_->cuda_stream); else #endif copy(source, target); diff --git a/src/models/model.cpp b/src/models/model.cpp index 993b66248..a31b1ed84 100644 --- a/src/models/model.cpp +++ b/src/models/model.cpp @@ -12,7 +12,7 @@ namespace Generators { -State::State(const GeneratorParams& params) : params_{params} { +State::State(const GeneratorParams& params) : params_{params.shared_from_this()} { } void State::Run(OrtSession& session) { @@ -94,13 +94,13 @@ void CheckResult(tfmError_t error) { } TokenizerStream::TokenizerStream(const Tokenizer& tokenizer) - : tokenizer_{tokenizer} { + : tokenizer_{tokenizer.shared_from_this()} { CheckResult(TfmCreate(kTfmKindDetokenizerCache, cache_.Address())); } const std::string& TokenizerStream::Decode(int32_t token) { const char* string; - CheckResult(TfmDetokenizeCached(tokenizer_.tokenizer_, cache_, token, &string)); + CheckResult(TfmDetokenizeCached(tokenizer_->tokenizer_, cache_, token, &string)); chunk_ = string; return chunk_; } @@ -297,23 +297,32 @@ void Model::CreateSessionOptions() { } } -std::unique_ptr Model::CreateTokenizer() const { - return std::make_unique(*config_); +std::shared_ptr Model::CreateTokenizer() const { + return std::make_shared(*config_); } -std::unique_ptr CreateModel(OrtEnv& ort_env, const char* config_path) { +std::shared_ptr CreateModel(OrtEnv& ort_env, const char* config_path) { auto config = std::make_unique(config_path); if (config->model.type == "gpt2") - return std::make_unique(std::move(config), ort_env); + return std::make_shared(std::move(config), ort_env); if (config->model.type == "llama" || config->model.type == "gemma" || config->model.type == "mistral" || config->model.type == "phi") - return std::make_unique(std::move(config), ort_env); + return std::make_shared(std::move(config), ort_env); if (config->model.type == "whisper") - return std::make_unique(std::move(config), ort_env); + return std::make_shared(std::move(config), ort_env); throw std::runtime_error("Unsupported model_type in config.json: " + config->model.type); } +std::shared_ptr CreateGeneratorParams(const Model& model) { + return std::make_shared(model); +} + +// Used by benchmarking tests only, should not be used normally +std::shared_ptr CreateGeneratorParams() { + return std::make_shared(); +} + #if USE_CUDA void ConvertFp16ToFp32(OrtAllocator& allocator, cudaStream_t stream, OrtValue& in, std::unique_ptr& p_out) { auto shape_info = in.GetTensorTypeAndShapeInfo(); diff --git a/src/models/model.h b/src/models/model.h index 3f1d4ceca..9af784362 100644 --- a/src/models/model.h +++ b/src/models/model.h @@ -15,7 +15,7 @@ struct State { virtual RoamingArray Run(int current_length, RoamingArray next_tokens, RoamingArray next_indices = {}) = 0; - const GeneratorParams& params_; + std::shared_ptr params_; std::vector input_names_, output_names_; std::vector inputs_, outputs_; @@ -57,7 +57,7 @@ struct TokenizerStream { const std::string& Decode(int32_t token); private: - const Tokenizer& tokenizer_; + std::shared_ptr tokenizer_; TfmPtr cache_; std::string chunk_; }; @@ -66,7 +66,7 @@ struct TokenizerStream { // Sequence length is vector.size()/count std::vector PadInputs(std::span > sequences, int32_t pad_token_id); -struct Tokenizer { +struct Tokenizer : std::enable_shared_from_this { Tokenizer(Config& config); std::unique_ptr CreateStream() const; @@ -78,6 +78,7 @@ struct Tokenizer { std::vector DecodeBatch(std::span sequences, size_t count) const; TfmPtr tokenizer_; + std::shared_ptr external_owner_; // Set to 'this' when created by the C API to preserve lifetime private: int32_t pad_token_id_; @@ -94,11 +95,11 @@ struct SessionInfo { std::unordered_map inputs_, outputs_; }; -struct Model { +struct Model : std::enable_shared_from_this { Model(std::unique_ptr config); virtual ~Model(); - std::unique_ptr CreateTokenizer() const; + std::shared_ptr CreateTokenizer() const; virtual std::unique_ptr CreateState(RoamingArray sequence_lengths, const GeneratorParams& params) const = 0; @@ -113,6 +114,8 @@ struct Model { std::unique_ptr session_info_; + std::shared_ptr external_owner_; // Set to 'this' when created by the C API to preserve lifetime + protected: void InitDeviceAllocator(OrtSession& session); void CreateSessionOptions(); diff --git a/src/models/onnxruntime_inline.h b/src/models/onnxruntime_inline.h index 1367ac643..cd7180e93 100644 --- a/src/models/onnxruntime_inline.h +++ b/src/models/onnxruntime_inline.h @@ -151,7 +151,7 @@ inline std::unique_ptr Allocator::Create(const OrtSession& sess, cons } inline void SetCurrentGpuDeviceId(int device_id) { - api->SetCurrentGpuDeviceId(device_id); + ThrowOnError(api->SetCurrentGpuDeviceId(device_id)); } inline int GetCurrentGpuDeviceId() { diff --git a/src/models/position_ids.cpp b/src/models/position_ids.cpp index bfff6e161..a0e8d6b56 100644 --- a/src/models/position_ids.cpp +++ b/src/models/position_ids.cpp @@ -12,7 +12,7 @@ PositionIDs::PositionIDs(const Model& model, State& state, RoamingArray if (type_ != Ort::TypeToTensorType::type && type_ != Ort::TypeToTensorType::type) throw std::runtime_error("position_ids & attention_mask only support int32 or int64 types"); - std::array shape{state_.params_.batch_size, state_.params_.sequence_length}; // Only batch_size initially, as we haven't expanded over the beams yet + std::array shape{state_.params_->batch_size, state_.params_->sequence_length}; // Only batch_size initially, as we haven't expanded over the beams yet position_ids_ = OrtValue::CreateTensor(model.allocator_cpu_, shape, type_); position_ids_next_ = OrtValue::CreateTensor(model.allocator_cpu_, std::array{shape[0], 1}, type_); attention_mask_ = OrtValue::CreateTensor(model.allocator_cpu_, shape, type_); @@ -22,10 +22,10 @@ PositionIDs::PositionIDs(const Model& model, State& state, RoamingArray else InitializeTensors(shape, sequence_lengths_unk); - position_ids_ = model_.ExpandInputs(position_ids_, state_.params_.search.num_beams); - position_ids_next_ = model_.ExpandInputs(position_ids_next_, state_.params_.search.num_beams); - attention_mask_ = model_.ExpandInputs(attention_mask_, state_.params_.search.num_beams); - shape[0] *= state_.params_.search.num_beams; + position_ids_ = model_.ExpandInputs(position_ids_, state_.params_->search.num_beams); + position_ids_next_ = model_.ExpandInputs(position_ids_next_, state_.params_->search.num_beams); + attention_mask_ = model_.ExpandInputs(attention_mask_, state_.params_->search.num_beams); + shape[0] *= state_.params_->search.num_beams; position_ids_shape_ = shape; attention_mask_shape_ = shape; } @@ -106,13 +106,13 @@ void PositionIDs::InitializeTensors(std::array shape, cpu_spanGetTensorMutableData(); auto* position_data = position_ids_->GetTensorMutableData(); auto* position_data_next = position_ids_next_->GetTensorMutableData(); - const auto* word_id = state_.params_.input_ids.data(); + const auto* word_id = state_.params_->input_ids.data(); auto* mask = mask_data; auto* position = position_data; for (int i = 0; i < shape[0]; i++) { T abs_position = 0; for (int j = 0; j < shape[1]; j++, word_id++, mask++, position++) { - if (*word_id == state_.params_.pad_token_id) { + if (*word_id == state_.params_->pad_token_id) { *mask = 0; *position = 0; } else { @@ -122,8 +122,8 @@ void PositionIDs::InitializeTensors(std::array shape, cpu_span(abs_position); + for (int k = 0; k < state_.params_->search.num_beams; k++) { + sequence_lengths[i * state_.params_->search.num_beams + k] = static_cast(abs_position); } } } diff --git a/src/models/whisper.cpp b/src/models/whisper.cpp index f6f2aaea1..5c8fe9d83 100644 --- a/src/models/whisper.cpp +++ b/src/models/whisper.cpp @@ -20,12 +20,12 @@ Whisper_State::Whisper_State(const Whisper_Model& model, RoamingArray s model_{model} { auto& inputs = const_cast(std::get(params.inputs)); - auto encoder_input_ids = model_.ExpandInputs(inputs.input_features, params_.search.num_beams); + auto encoder_input_ids = model_.ExpandInputs(inputs.input_features, params_->search.num_beams); encoder_hidden_states_ = OrtValue::CreateTensor(*model_.allocator_device_, std::array{decoder_input_ids_.GetShape()[0], 1500, 384}); auto sequence_lengths = sequence_lengths_unk.GetCPU(); for (int i = 0; i < decoder_input_ids_.GetShape()[0]; i++) { - sequence_lengths[i] = static_cast(params_.sequence_length); + sequence_lengths[i] = static_cast(params_->sequence_length); } input_names_.push_back("encoder_input_ids"); diff --git a/src/ort_genai_c.cpp b/src/ort_genai_c.cpp index 25b0edf18..1beb2a43b 100644 --- a/src/ort_genai_c.cpp +++ b/src/ort_genai_c.cpp @@ -63,14 +63,18 @@ const int32_t* OGA_API_CALL OgaSequencesGetSequenceData(const OgaSequences* p, s OgaResult* OGA_API_CALL OgaCreateModel(const char* config_path, OgaModel** out) { OGA_TRY - *out = reinterpret_cast(Generators::CreateModel(Generators::GetOrtEnv(), config_path).release()); + auto model = Generators::CreateModel(Generators::GetOrtEnv(), config_path); + model->external_owner_ = model; + *out = reinterpret_cast(model.get()); return nullptr; OGA_CATCH } OgaResult* OGA_API_CALL OgaCreateGeneratorParams(const OgaModel* model, OgaGeneratorParams** out) { OGA_TRY - *out = reinterpret_cast(new Generators::GeneratorParams(*reinterpret_cast(model))); + auto params = std::make_shared(*reinterpret_cast(model)); + params->external_owner_ = params; + *out = reinterpret_cast(params.get()); return nullptr; OGA_CATCH } @@ -145,9 +149,9 @@ OgaResult* OGA_API_CALL OgaGenerator_ComputeLogits(OgaGenerator* generator) { OGA_CATCH } -OgaResult* OGA_API_CALL OgaGenerator_GenerateNextToken_Top(OgaGenerator* generator) { +OgaResult* OGA_API_CALL OgaGenerator_GenerateNextToken(OgaGenerator* generator) { OGA_TRY - reinterpret_cast(generator)->GenerateNextToken_Top(); + reinterpret_cast(generator)->GenerateNextToken(); return nullptr; OGA_CATCH } @@ -164,7 +168,9 @@ const int32_t* OGA_API_CALL OgaGenerator_GetSequence(const OgaGenerator* oga_gen OgaResult* OGA_API_CALL OgaCreateTokenizer(const OgaModel* model, OgaTokenizer** out) { OGA_TRY - *out = reinterpret_cast(reinterpret_cast(model)->CreateTokenizer().release()); + auto tokenizer = reinterpret_cast(model)->CreateTokenizer(); + tokenizer->external_owner_ = tokenizer; + *out = reinterpret_cast(tokenizer.get()); return nullptr; OGA_CATCH } @@ -237,11 +243,11 @@ void OGA_API_CALL OgaDestroySequences(OgaSequences* p) { } void OGA_API_CALL OgaDestroyModel(OgaModel* p) { - delete reinterpret_cast(p); + reinterpret_cast(p)->external_owner_ = nullptr; } void OGA_API_CALL OgaDestroyGeneratorParams(OgaGeneratorParams* p) { - delete reinterpret_cast(p); + reinterpret_cast(p)->external_owner_ = nullptr; } void OGA_API_CALL OgaDestroyGenerator(OgaGenerator* p) { @@ -249,7 +255,7 @@ void OGA_API_CALL OgaDestroyGenerator(OgaGenerator* p) { } void OGA_API_CALL OgaDestroyTokenizer(OgaTokenizer* p) { - delete reinterpret_cast(p); + reinterpret_cast(p)->external_owner_ = nullptr; } void OGA_API_CALL OgaDestroyTokenizerStream(OgaTokenizerStream* p) { diff --git a/src/ort_genai_c.h b/src/ort_genai_c.h index 255bfbafb..fbd394f10 100644 --- a/src/ort_genai_c.h +++ b/src/ort_genai_c.h @@ -172,16 +172,7 @@ OGA_EXPORT bool OGA_API_CALL OgaGenerator_IsDone(const OgaGenerator* generator); * \return OgaResult containing the error message if the computation of the logits failed. */ OGA_EXPORT OgaResult* OGA_API_CALL OgaGenerator_ComputeLogits(OgaGenerator* generator); - -/* - * \brief Generates the next token based on the computed logits using the greedy search. - * \param[in] generator The generator to generate the next token for. - * \return OgaResult containing the error message if the generation of the next token failed. - */ -OGA_EXPORT OgaResult* OGA_API_CALL OgaGenerator_GenerateNextToken_Top(OgaGenerator* generator); - -OGA_EXPORT OgaResult* OGA_API_CALL OgaGenerator_GenerateNextToken_TopK(OgaGenerator* generator, int k, float t); -OGA_EXPORT OgaResult* OGA_API_CALL OgaGenerator_GenerateNextToken_TopP(OgaGenerator* generator, float p, float t); +OGA_EXPORT OgaResult* OGA_API_CALL OgaGenerator_GenerateNextToken(OgaGenerator* generator); /* * \brief Returns the number of tokens in the sequence at the given index. diff --git a/src/python/py/models/builder.py b/src/python/py/models/builder.py index 1652d5778..fa022f7aa 100644 --- a/src/python/py/models/builder.py +++ b/src/python/py/models/builder.py @@ -116,7 +116,7 @@ def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options): "op_type": "MultiHeadAttention", # Attention op to use "use_gqa": ep == "cuda" and io_dtype == TensorProto.FLOAT16 # Check if GroupQueryAttention can be used } - if self.attention_attrs["use_gqa"] or self.num_attn_heads != self.num_kv_heads: + if self.attention_attrs["use_gqa"]: self.attention_attrs["op_type"] = "GroupQueryAttention" # Quantization-specific variables (INT4, INT8, etc.) @@ -166,15 +166,15 @@ def make_genai_config(self, model_name_or_path, extra_kwargs, out_dir): "do_sample": config.do_sample if hasattr(config, "do_sample") else False, "early_stopping": True, "length_penalty": config.length_penalty if hasattr(config, "length_penalty") else 1.0, - "max_length": config.max_length if hasattr(config, "max_length") else 20, + "max_length": self.context_length, "min_length": 0, "no_repeat_ngram_size": config.no_repeat_ngram_size if hasattr(config, "no_repeat_ngram_size") else 0, "num_beams": config.num_beams if hasattr(config, "num_beams") else 1, "num_return_sequences": config.num_return_sequences if hasattr(config, "num_return_sequences") else 1, - "past_present_share_buffer": True if self.attention_attrs["op_type"] == "GroupQueryAttention" else False, + "past_present_share_buffer": self.attention_attrs["op_type"] == "GroupQueryAttention", "repetition_penalty": config.repetition_penalty if hasattr(config, "repetition_penalty") else 1.0, "temperature": config.temperature if hasattr(config, "temperature") else 1.0, - "top_k": config.top_k if hasattr(config, "top_k") else 50, + "top_k": 1, "top_p": config.top_p if hasattr(config, "top_p") else 1.0, }, } @@ -337,7 +337,7 @@ def make_constant(self, name): path = name.split("/") onnx_dtype, dims, num = eval(path[-3]), path[-2], eval(path[-1]) np_dtype = self.to_numpy_dtype[onnx_dtype] - value = numpy_helper.from_array(np.array(num if dims == "0D" else [num], dtype=np_dtype), name=name.replace("constants", "numpy_helper")) + value = numpy_helper.from_array(np.array(num if dims == "0D" else list(num) if type(num) == tuple else [num], dtype=np_dtype), name=name.replace("constants", "numpy_helper")) node_name = name.replace("constants", "constant_nodes") self.make_node("Constant", inputs=[], outputs=[name], name=node_name, value=value) @@ -349,10 +349,10 @@ def make_gather(self, name, inputs, axis): self.make_node("Gather", inputs=inputs, outputs=[output], name=name, axis=axis) self.make_value_info(output, TensorProto.INT64, shape=[]) - def make_reshape(self, name, inputs): + def make_reshape(self, name, inputs, dtype, shape): output = f"{name}/output_0" self.make_node("Reshape", inputs=inputs, outputs=[output], name=name) - self.make_value_info(output, TensorProto.INT64, shape=None) + self.make_value_info(output, dtype, shape=shape) def make_shape(self, name, root_input, shape): output = f"{name}/output_0" @@ -379,10 +379,10 @@ def make_concat(self, name, inputs, dtype, shape, axis=0): self.make_node("Concat", inputs=inputs, outputs=[output], name=name, axis=axis) self.make_value_info(output, dtype, shape=shape) - def make_equal(self, name, inputs): + def make_equal(self, name, inputs, shape): output = f"{name}/output_0" self.make_node("Equal", inputs=inputs, outputs=[output], name=name) - self.make_value_info(output, TensorProto.BOOL, shape=[4]) + self.make_value_info(output, TensorProto.BOOL, shape=shape) def make_where(self, name, inputs, dtype, shape): output = f"{name}/output_0" @@ -439,6 +439,11 @@ def make_mul(self, name, inputs, dtype, shape): self.make_node("Mul", inputs=inputs, outputs=[output], name=name) self.make_value_info(output, dtype, shape=shape) + def make_transpose(self, name, root_input, dtype, shape, perm): + output = f"{name}/output_0" + self.make_node("Transpose", inputs=[root_input], outputs=[output], perm=perm) + self.make_value_info(output, dtype, shape=shape) + def make_matmul(self, matmul, name, root_input, **kwargs): self.make_matmul_fp16_or_fp32(matmul, name, root_input, **kwargs) @@ -512,7 +517,6 @@ def make_embedding(self, embedding): self.layernorm_attrs["root_input"] = layernorm_attrs_value self.layernorm_attrs["skip_input"] = layernorm_attrs_value - def make_layernorm(self, layer_id, layernorm, skip, simple, location): root_input = self.layernorm_attrs["root_input"] skip_input = self.layernorm_attrs["skip_input"] @@ -552,7 +556,7 @@ def make_layernorm(self, layer_id, layernorm, skip, simple, location): return output_0 - def make_rotary_embedding(self, rotemb, name, root_input, **kwargs): + def make_rotary_embedding_caches(self, rotemb): cos_cache_name, sin_cache_name = "cos_cache", "sin_cache" if self.rotemb_attrs["create_rotary_embedding_caches"]: @@ -576,11 +580,195 @@ def make_rotary_embedding(self, rotemb, name, root_input, **kwargs): self.rotemb_attrs["create_rotary_embedding_caches"] = False + return cos_cache_name, sin_cache_name + + def make_rotary_embedding(self, rotemb, name, root_input, **kwargs): + cos_cache_name, sin_cache_name = self.make_rotary_embedding_caches(rotemb) + inputs = [root_input, kwargs.pop("position_ids"), cos_cache_name, sin_cache_name] output = f"{name}/output_0" self.make_node("RotaryEmbedding", inputs=inputs, outputs=[output], name=name, domain="com.microsoft", interleaved=0, **kwargs) self.make_value_info(output, self.io_dtype, shape=['batch_size', 'sequence_length', self.head_size * (self.num_kv_heads if "k_rotary" in name else self.num_attn_heads)]) + # TODO: This function and any corresponding changes to support it are temporary until ORT supports GQA for CPU + def make_repeat_kv(self, layer_id, root_input, past_kv, present_kv, **kwargs): + # Make subgraph that repeats tensor of shape (batch_size, sequence_length, num_kv_heads, head_size) + # to shape (batch_size, sequence_length, num_attn_heads, head_size) in an interleaved pattern + # and updates the KV caches + # + # root_input + # | + # Reshape + # | + # Transpose + # | + # | past_kv + # | / + # Concat + # | \ + # | present_kv + # | + # +-------+---------+ + # | | + # | Shape + # | | + # | +-----------+-----------+-----------+ + # | | | | | + # | Gather Gather Gather Gather + # | (idx=0) (idx=1) (idx=2) (idx=3) + # | | | | | + # | Unsqueeze Unsqueeze Unsqueeze Unsqueeze + # | | | | | + # | +-----------+-----------+-----------+ + # | | + # | +-----------------------+ + # | | | + # | | Mul + # | | | + # | Concat Concat + # | (5D) (4D) + # | | | + # | Reshape | + # | / | \ | + # | / | \ | + # | / | \ / + # | / | \ / + # | / | \ / + # | / Shape \ / + # | / | \ / + # | | ConstantOfShape \ / + # | \ | \ \ / + # | \ | Mul | / + # | \ | | / / + # | \ | Equal / + # | \ | / / + # \ \ | / / + # \ \ | / / + # \ \ | / / + # \ \ | / / + # Unsqueeze Where / + # \ / / + # \ / / + # \ / / + # \ / / + # Expand / + # | / + # | / + # | / + # | / + # | / + # Reshape + # | + # Transpose + # | + # Reshape + basename = f"/model/layers.{layer_id}/attn/{'k_proj' if past_kv.endswith('key') else 'v_proj'}/repeat_kv" + + # Make the initial subgraph + # + # +------> Gather --> Unsqueeze -----+ + # | | + # past_kv +------> Gather --> Unsqueeze -----+---> Mul --> Concat (4D) + # | | | + # root_input --> Reshape --> Transpose --> Concat --> Shape ---> Gather --> Unsqueeze -----+---> Concat (5D) + # | | | + # present_kv +------> Gather --> Unsqueeze -----+ + reshape_1_name = f"{basename}/Reshape_1" + reshape_1_inputs = [root_input, f"/model/constants/TensorProto.INT64/1D/0, 0, {self.num_kv_heads}, -1"] + self.make_reshape(reshape_1_name, reshape_1_inputs, dtype=self.io_dtype, shape=['batch_size', 'sequence_length', self.num_kv_heads, self.head_size]) + transpose_1_name = f"{basename}/Transpose_1" + transpose_1_input = f"{reshape_1_name}/output_0" + self.make_transpose(transpose_1_name, transpose_1_input, dtype=self.io_dtype, shape=['batch_size', self.num_kv_heads, 'sequence_length', self.head_size], perm=[0,2,1,3]) + concat_1_name = f"{basename}/Concat_1" + concat_1_inputs = [past_kv, f"{transpose_1_name}/output_0"] + self.make_node("Concat", inputs=concat_1_inputs, outputs=[present_kv], name=concat_1_name, axis=2) + + shape_1_name = f"{basename}/Shape_1" + self.make_shape(shape_1_name, present_kv, shape=[4]) + gather_1_name = f"{basename}/Gather_1" + gather_1_inputs = [f"{shape_1_name}/output_0", "/model/constants/TensorProto.INT64/0D/0"] + self.make_gather(gather_1_name, gather_1_inputs, axis=0) + unsqueeze_1_name = f"{basename}/Unsqueeze_1" + unsqueeze_1_inputs = [f"{gather_1_name}/output_0", "/model/constants/TensorProto.INT64/1D/0"] + self.make_unsqueeze(unsqueeze_1_name, unsqueeze_1_inputs, dtype=TensorProto.INT64, shape=[1]) + gather_2_name = f"{basename}/Gather_2" + gather_2_inputs = [f"{shape_1_name}/output_0", "/model/constants/TensorProto.INT64/0D/1"] + self.make_gather(gather_2_name, gather_2_inputs, axis=0) + unsqueeze_2_name = f"{basename}/Unsqueeze_2" + unsqueeze_2_inputs = [f"{gather_2_name}/output_0", "/model/constants/TensorProto.INT64/1D/0"] + self.make_unsqueeze(unsqueeze_2_name, unsqueeze_2_inputs, dtype=TensorProto.INT64, shape=[1]) + gather_3_name = f"{basename}/Gather_3" + gather_3_inputs = [f"{shape_1_name}/output_0", "/model/constants/TensorProto.INT64/0D/2"] + self.make_gather(gather_3_name, gather_3_inputs, axis=0) + unsqueeze_3_name = f"{basename}/Unsqueeze_3" + unsqueeze_3_inputs = [f"{gather_3_name}/output_0", "/model/constants/TensorProto.INT64/1D/0"] + self.make_unsqueeze(unsqueeze_3_name, unsqueeze_3_inputs, dtype=TensorProto.INT64, shape=[1]) + gather_4_name = f"{basename}/Gather_4" + gather_4_inputs = [f"{shape_1_name}/output_0", "/model/constants/TensorProto.INT64/0D/3"] + self.make_gather(gather_4_name, gather_4_inputs, axis=0) + unsqueeze_4_name = f"{basename}/Unsqueeze_4" + unsqueeze_4_inputs = [f"{gather_4_name}/output_0", "/model/constants/TensorProto.INT64/1D/0"] + self.make_unsqueeze(unsqueeze_4_name, unsqueeze_4_inputs, dtype=TensorProto.INT64, shape=[1]) + concat_2_name = f"{basename}/Concat_2" + concat_2_inputs = [f"{unsqueeze_1_name}/output_0", f"{unsqueeze_2_name}/output_0", f"/model/constants/TensorProto.INT64/1D/{self.num_attn_heads // self.num_kv_heads}", f"{unsqueeze_3_name}/output_0", f"{unsqueeze_4_name}/output_0"] + self.make_concat(concat_2_name, concat_2_inputs, dtype=TensorProto.INT64, shape=[5], axis=0) + + mul_1_name = f"{basename}/Mul_1" + mul_1_inputs = [f"{unsqueeze_2_name}/output_0", f"/model/constants/TensorProto.INT64/0D/{self.num_attn_heads // self.num_kv_heads}"] + self.make_mul(mul_1_name, mul_1_inputs, dtype=TensorProto.INT64, shape=None) + concat_3_name = f"{basename}/Concat_3" + concat_3_inputs = [f"{unsqueeze_1_name}/output_0", f"{mul_1_name}/output_0", f"{unsqueeze_3_name}/output_0", f"{unsqueeze_4_name}/output_0"] + self.make_concat(concat_3_name, concat_3_inputs, dtype=TensorProto.INT64, shape=[4], axis=0) + + # Make the subgraph that follows the initial subgraph + # + # Mul ---> Equal + # / \ + # Reshape --> Shape --> ConstantOfShape --> Where + # | | + # +----------------------------------------+ + reshape_2_name = f"{basename}/Reshape_2" + reshape_2_inputs = [f"{concat_2_name}/output_0", "/model/constants/TensorProto.INT64/1D/-1"] + self.make_reshape(reshape_2_name, reshape_2_inputs, dtype=TensorProto.INT64, shape=None) + shape_2_name = f"{basename}/Shape_2" + self.make_shape(shape_2_name, f"{reshape_2_name}/output_0", shape=[1]) + constant_shape_name = f"{basename}/ConstantOfShape" + constant_shape_value = numpy_helper.from_array(np.array([1], dtype="int64")) + self.make_constant_of_shape(constant_shape_name, f"{shape_2_name}/output_0", value=constant_shape_value, dtype=TensorProto.INT64, shape=[5]) + mul_2_name = f"{basename}/Mul" + mul_2_inputs = [f"{constant_shape_name}/output_0", "/model/constants/TensorProto.INT64/0D/-1"] + self.make_mul(mul_2_name, mul_2_inputs, dtype=TensorProto.INT64, shape=[5]) + equal_name = f"{basename}/Equal" + equal_inputs = [f"{reshape_2_name}/output_0", f"{mul_2_name}/output_0"] + self.make_equal(equal_name, equal_inputs, shape=[5]) + where_name = f"{basename}/Where" + where_inputs = [f"{equal_name}/output_0", f"{constant_shape_name}/output_0", f"{reshape_2_name}/output_0"] + self.make_where(where_name, where_inputs, dtype=TensorProto.INT64, shape=[5]) + + # Make the final nodes + # + # Where (from above) Concat (from above) + # \ \ + # Unsqueeze --> Expand --> Reshape --> Transpose --> Reshape + unsqueeze_5_name = f"{basename}/Unsqueeze_5" + unsqueeze_5_inputs = [present_kv, "/model/constants/TensorProto.INT64/1D/2"] + self.make_unsqueeze(unsqueeze_5_name, unsqueeze_5_inputs, dtype=self.io_dtype, shape=['batch_size', self.num_kv_heads, 1, 'sequence_length', self.head_size]) + expand_name = f"{basename}/Expand" + expand_inputs = [f"{unsqueeze_5_name}/output_0", f"{where_name}/output_0"] + self.make_expand(expand_name, expand_inputs, dtype=self.io_dtype, shape=['batch_size', self.num_kv_heads, self.num_attn_heads // self.num_kv_heads, 'sequence_length', self.head_size]) + reshape_3_name = f"{basename}/Reshape_3" + reshape_3_inputs = [f"{expand_name}/output_0", f"{concat_3_name}/output_0"] + self.make_reshape(reshape_3_name, reshape_3_inputs, dtype=self.io_dtype, shape=['batch_size', self.num_attn_heads, 'sequence_length', self.head_size]) + transpose_2_name = f"{basename}/Transpose_2" + transpose_2_input = f"{reshape_3_name}/output_0" + self.make_transpose(transpose_2_name, transpose_2_input, dtype=self.io_dtype, shape=['batch_size', 'sequence_length', self.num_attn_heads, self.head_size], perm=[0,2,1,3]) + reshape_4_name = f"{basename}/Reshape_4" + reshape_4_inputs = [f"{transpose_2_name}/output_0", f"/model/constants/TensorProto.INT64/1D/0, 0, {self.num_attn_heads * self.head_size}"] + self.make_reshape(reshape_4_name, reshape_4_inputs, dtype=self.io_dtype, shape=['batch_size', 'sequence_length', self.num_attn_heads * self.head_size]) + + input_to_attention = f"{reshape_4_name}/output_0" + return input_to_attention + def make_attention_op(self, name, **kwargs): op_type = self.attention_attrs["op_type"] @@ -648,13 +836,20 @@ def make_attention(self, layer_id, attention, root_input, **kwargs): # | # O_Add + q_input_to_attention = "" + k_input_to_attention = "" + v_input_to_attention = "" + # Make MatMul nodes q_matmul_name = f"/model/layers.{layer_id}/attn/q_proj/MatMul" self.make_matmul(attention.q_proj.weight.detach().numpy(), q_matmul_name, root_input) + q_input_to_attention = f"{q_matmul_name}/output_0" k_matmul_name = f"/model/layers.{layer_id}/attn/k_proj/MatMul" self.make_matmul(attention.k_proj.weight.detach().numpy(), k_matmul_name, root_input) + k_input_to_attention = f"{k_matmul_name}/output_0" v_matmul_name = f"/model/layers.{layer_id}/attn/v_proj/MatMul" self.make_matmul(attention.v_proj.weight.detach().numpy(), v_matmul_name, root_input) + v_input_to_attention = f"{v_matmul_name}/output_0" # Make Add nodes (if bias exists) q_bias_exists = attention.q_proj.bias is not None @@ -664,27 +859,42 @@ def make_attention(self, layer_id, attention, root_input, **kwargs): if q_bias_exists: q_add_name = f"/model/layers.{layer_id}/attn/q_proj/Add" self.make_add_bias(attention.q_proj.bias.detach().numpy(), q_add_name, root_input=f"{q_matmul_name}/output_0") + q_input_to_attention = f"{q_add_name}/output_0" if k_bias_exists: k_add_name = f"/model/layers.{layer_id}/attn/k_proj/Add" self.make_add_bias(attention.k_proj.bias.detach().numpy(), k_add_name, root_input=f"{k_matmul_name}/output_0") + k_input_to_attention = f"{k_add_name}/output_0" if v_bias_exists: v_add_name = f"/model/layers.{layer_id}/attn/v_proj/Add" self.make_add_bias(attention.v_proj.bias.detach().numpy(), v_add_name, root_input=f"{v_matmul_name}/output_0") + v_input_to_attention = f"{v_add_name}/output_0" # Make RotaryEmbedding nodes q_rotary_name = f"/model/layers.{layer_id}/attn/q_rotary/RotaryEmbedding" q_rotary_input = f"{q_matmul_name if not q_bias_exists else q_add_name}/output_0" self.make_rotary_embedding(attention.rotary_emb, q_rotary_name, q_rotary_input, position_ids=kwargs.get("position_ids", "position_ids")) + q_input_to_attention = f"{q_rotary_name}/output_0" + k_rotary_name = f"/model/layers.{layer_id}/attn/k_rotary/RotaryEmbedding" k_rotary_input = f"{k_matmul_name if not k_bias_exists else k_add_name}/output_0" self.make_rotary_embedding(attention.rotary_emb, k_rotary_name, k_rotary_input, position_ids=kwargs.get("position_ids", "position_ids")) + k_input_to_attention = f"{k_rotary_name}/output_0" + + # Make repeat KV nodes (TODO: remove once ORT supports GQA for CPU) + past_k = f"past_key_values.{layer_id}.key" + past_v = f"past_key_values.{layer_id}.value" + present_k = f"present.{layer_id}.key" + present_v = f"present.{layer_id}.value" + if self.num_attn_heads != self.num_kv_heads and not self.attention_attrs['use_gqa']: + k_input_to_attention = self.make_repeat_kv(layer_id, k_input_to_attention, past_k, present_k) + v_input_to_attention = self.make_repeat_kv(layer_id, v_input_to_attention, past_v, present_v) + past_k, past_v, present_k, present_v = "", "", "", "" # Make attention node (e.g. MultiHeadAttention, GroupQueryAttention, etc.) attn_name = f"/model/layers.{layer_id}/attn/{self.attention_attrs['op_type']}" self.make_attention_op( - attn_name, q_path=f"{q_rotary_name}/output_0", k_path=f"{k_rotary_name}/output_0", v_path=f"{v_matmul_name if not v_bias_exists else v_add_name}/output_0", - past_k=f"past_key_values.{layer_id}.key", past_v=f"past_key_values.{layer_id}.value", - present_k=f"present.{layer_id}.key", present_v=f"present.{layer_id}.value", **kwargs, + attn_name, q_path=q_input_to_attention, k_path=k_input_to_attention, v_path=v_input_to_attention, + past_k=past_k, past_v=past_v, present_k=present_k, present_v=present_v, **kwargs, ) # Make MatMul node (output projection weight node) @@ -938,8 +1148,8 @@ def make_attention_mask_reformatting(self): # TODO: replace Concat with Expand for performance gains concat_name = f"{basename}/Concat" - concat_inputs = [f"{end_add_name}/output_0" for _ in range(self.num_kv_heads)] - concat_shape = ["batch_size", self.num_kv_heads, "source_sequence_length", "target_sequence_length"] + concat_inputs = [f"{end_add_name}/output_0" for _ in range(self.num_attn_heads)] + concat_shape = ["batch_size", self.num_attn_heads, "source_sequence_length", "target_sequence_length"] self.make_concat(concat_name, concat_inputs, dtype=self.io_dtype, shape=concat_shape, axis=1) # Shape of mask is now (B, N, S, T) self.mask_attrs["mask_name"] = concat_name @@ -1027,7 +1237,7 @@ def make_input_ids_subgraph(self, basename, past_key_gather_name): # Merged path reshape_name = f"{basename}/Reshape" reshape_inputs = [f"{add_2_name}/output_0", f"{concat_3_name}/output_0"] - self.make_reshape(reshape_name, reshape_inputs) + self.make_reshape(reshape_name, reshape_inputs, dtype=TensorProto.INT64, shape=None) less_name = f"{basename}/Less" less_inputs = [f"{range_name}/output_0", f"{reshape_name}/output_0"] self.make_less(less_name, less_inputs) @@ -1147,7 +1357,7 @@ def make_common_mask_reformat_subgraph(self, basename, root_input, unsqueeze_for self.make_mul(mul_name, mul_inputs, dtype=TensorProto.INT64, shape=["unk"]) equal_name = f"{basename}/Equal" equal_inputs = [f"{concat_name}/output_0", f"{mul_name}/output_0"] - self.make_equal(equal_name, equal_inputs) + self.make_equal(equal_name, equal_inputs, shape=[4]) where_name = f"{basename}/Where_1" where_inputs = [f"{equal_name}/output_0", f"{constant_shape_name}/output_0", f"{concat_name}/output_0"] @@ -1159,7 +1369,7 @@ def make_common_mask_reformat_subgraph(self, basename, root_input, unsqueeze_for self.make_expand(expand_name, expand_inputs, dtype=expand_dtype, shape=expand_shape) return expand_name - + class LlamaModel(Model): def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options): @@ -1247,7 +1457,7 @@ def make_position_ids_reformatting(self): self.make_concat(concat_name, concat_inputs, dtype=TensorProto.INT64, shape=[2], axis=0) reshape_name = f"{basename}/Reshape" reshape_inputs = ["position_ids", f"{concat_name}/output_0"] - self.make_reshape(reshape_name, reshape_inputs) + self.make_reshape(reshape_name, reshape_inputs, dtype=TensorProto.INT64, shape=None) return reshape_name @@ -1265,54 +1475,6 @@ def __init__(self, config, io_dtype, onnx_dtype, ep, cache_dir, extra_options): def make_rotary_embedding(self, rotemb, name, root_input, **kwargs): super().make_rotary_embedding(rotemb, name, root_input, num_heads=self.rotemb_attrs["num_heads"], rotary_embedding_dim=self.rotemb_attrs["rotary_embedding_dim"], **kwargs) - - def make_group_query_attention(self, name, **kwargs): - if self.layer_id < self.num_layers - 3: - super().make_group_query_attention(name, **kwargs) - return - - # Cast inputs and outputs of GroupQueryAttention - input_kwargs = {"q_path", "k_path", "v_path", "past_k", "past_v"} - new_kwargs = {} - - # Make input cast nodes to bfloat16 - for input_name in input_kwargs: - cast_name = f"/model/layers.{self.layer_id}/attn/{input_name.replace('path', 'proj')}/Cast" - cast_shape = ['batch_size', 'sequence_length', self.hidden_size] if input_name in {"q_path", "k_path", "v_path"} else ["batch_size", self.num_kv_heads, "past_sequence_length", self.head_size] - self.make_cast(cast_name, kwargs[input_name], dtype=TensorProto.BFLOAT16, shape=cast_shape) - new_kwargs[input_name] = f"{cast_name}/output_0" - - # Make GroupQueryAttention node - inputs = [ - new_kwargs["q_path"], new_kwargs["k_path"], new_kwargs["v_path"], - new_kwargs["past_k"], new_kwargs["past_v"], - kwargs.get("seqlens_k", ""), kwargs.get("total_seq_len", ""), - ] - outputs = [f"{name}/Cast/output_0", f"{name}/output_1", f"{name}/output_2"] - self.make_node("GroupQueryAttention", inputs=inputs, outputs=outputs, name=name, domain="com.microsoft", num_heads=self.num_attn_heads, kv_num_heads=self.num_kv_heads) - self.make_value_info(outputs[0], TensorProto.BFLOAT16, shape=['batch_size', 'sequence_length', self.hidden_size]) - - present_kv_shape = ["batch_size", self.num_kv_heads, "total_sequence_length", self.head_size] - self.make_value_info(outputs[1], TensorProto.BFLOAT16, shape=present_kv_shape) - self.make_value_info(outputs[2], TensorProto.BFLOAT16, shape=present_kv_shape) - - # Make output cast nodes to float16 - target_dtype = TensorProto.FLOAT16 - - cast_o_path_name = f"{name}/o_proj/Cast" - cast_o_path_output = f"{name}/output_0" - self.make_node("Cast", inputs=[outputs[0]], outputs=[cast_o_path_output], name=cast_o_path_name, to=target_dtype) - self.make_value_info(cast_o_path_output, target_dtype, shape=['batch_size', 'sequence_length', self.hidden_size]) - - cast_present_k_name = f"{name}/present_k/Cast" - cast_present_k_output = f"present.{self.layer_id}.key" - self.make_node("Cast", inputs=[outputs[1]], outputs=[cast_present_k_output], name=cast_present_k_name, to=target_dtype) - self.make_value_info(cast_present_k_output, target_dtype, shape=present_kv_shape) - - cast_present_v_name = f"{name}/present_v/Cast" - cast_present_v_output = f"present.{self.layer_id}.value" - self.make_node("Cast", inputs=[outputs[2]], outputs=[cast_present_v_output], name=cast_present_v_name, to=target_dtype) - self.make_value_info(cast_present_v_output, target_dtype, shape=present_kv_shape) def make_mlp(self, layer_id, mlp, root_input): # Make nodes for the MLP subgraph diff --git a/src/python/python.cpp b/src/python/python.cpp index 6bf85d89f..584beb97c 100644 --- a/src/python/python.cpp +++ b/src/python/python.cpp @@ -3,6 +3,7 @@ #include #include #include "../generators.h" +#include "../json.h" #include "../search.h" #include "../models/model.h" @@ -52,32 +53,44 @@ void Declare_DeviceArray(pybind11::module& m, const char* name) { "get_array", [](Type& t) -> pybind11::array_t { return t.GetNumpy(); }, pybind11::return_value_policy::reference_internal); } -struct PyGeneratorParams : GeneratorParams { +struct PyGeneratorParams { + PyGeneratorParams(const Model& model) : params_{std::make_shared(model)} { + } + + operator const GeneratorParams&() const { return *params_; } + + std::shared_ptr params_; + // Turn the python py_input_ids_ into the low level parameters void Prepare() { // TODO: This will switch to using the variant vs being ifs if (py_input_ids_.size() != 0) { if (py_input_ids_.ndim() == 1) { // Just a 1D array - batch_size = 1; - sequence_length = static_cast(py_input_ids_.shape(0)); + params_->batch_size = 1; + params_->sequence_length = static_cast(py_input_ids_.shape(0)); } else { if (py_input_ids_.ndim() != 2) throw std::runtime_error("Input IDs can only be 1 or 2 dimensional"); - batch_size = static_cast(py_input_ids_.shape(0)); - sequence_length = static_cast(py_input_ids_.shape(1)); + params_->batch_size = static_cast(py_input_ids_.shape(0)); + params_->sequence_length = static_cast(py_input_ids_.shape(1)); } - input_ids = ToSpan(py_input_ids_); + params_->input_ids = ToSpan(py_input_ids_); } if (py_whisper_input_features_.size() != 0) { - GeneratorParams::Whisper& whisper = inputs.emplace(); + GeneratorParams::Whisper& whisper = params_->inputs.emplace(); +#ifdef __APPLE__ + std::span shape(reinterpret_cast(py_whisper_input_features_.shape()), + py_whisper_input_features_.ndim()); +#else std::span shape(py_whisper_input_features_.shape(), py_whisper_input_features_.ndim()); +#endif whisper.input_features = OrtValue::CreateTensor(Ort::Allocator::GetWithDefaultOptions().GetInfo(), ToSpan(py_whisper_input_features_), shape); whisper.decoder_input_ids = ToSpan(py_whisper_decoder_input_ids_); - batch_size = 1; - sequence_length = static_cast(py_whisper_decoder_input_ids_.shape(1)); - input_ids = ToSpan(py_whisper_decoder_input_ids_); + params_->batch_size = 1; + params_->sequence_length = static_cast(py_whisper_decoder_input_ids_.shape(1)); + params_->input_ids = ToSpan(py_whisper_decoder_input_ids_); } } @@ -86,14 +99,14 @@ struct PyGeneratorParams : GeneratorParams { auto name = entry.first.cast(); try { if (pybind11::isinstance(entry.second)) { - SetSearchNumber(search, name, entry.second.cast()); + SetSearchNumber(params_->search, name, entry.second.cast()); } else if (pybind11::isinstance(entry.second)) { - SetSearchBool(search, name, entry.second.cast()); + SetSearchBool(params_->search, name, entry.second.cast()); } else if (pybind11::isinstance(entry.second)) { - SetSearchNumber(search, name, entry.second.cast()); + SetSearchNumber(params_->search, name, entry.second.cast()); } else - throw std::runtime_error("Unknown search option type, can be float/bool/int"); - } catch (const std::exception& e) { + throw std::runtime_error("Unknown search option type, can be float/bool/int:" + name); + } catch (JSON::unknown_value_error& e) { throw std::runtime_error("Unknown search option:" + name); } } @@ -124,22 +137,6 @@ struct PyGenerator { generator_->ComputeLogits(); } - void GenerateNextToken_TopK_TopP(int top_k, float top_p, float temperature) { - generator_->GenerateNextToken_TopK_TopP(top_k, top_p, temperature); - } - - void GenerateNextToken_TopP(float p, float temperature) { - generator_->GenerateNextToken_TopP(p, temperature); - } - - void GenerateNextToken_TopK(int k, float temperature) { - generator_->GenerateNextToken_TopK(k, temperature); - } - - void GenerateNextToken_Top() { - generator_->GenerateNextToken_Top(); - } - void GenerateNextToken() { generator_->GenerateNextToken(); } @@ -176,9 +173,9 @@ PYBIND11_MODULE(onnxruntime_genai, m) { pybind11::class_(m, "GeneratorParams") .def(pybind11::init()) - .def_readonly("pad_token_id", &PyGeneratorParams::pad_token_id) - .def_readonly("eos_token_id", &PyGeneratorParams::eos_token_id) - .def_readonly("vocab_size", &PyGeneratorParams::vocab_size) + .def_property_readonly("pad_token_id", [](const PyGeneratorParams& v) { return v.params_->pad_token_id; }) + .def_property_readonly("eos_token_id", [](const PyGeneratorParams& v) { return v.params_->eos_token_id; }) + .def_property_readonly("vocab_size", [](const PyGeneratorParams& v) { return v.params_->vocab_size; }) .def_readwrite("input_ids", &PyGeneratorParams::py_input_ids_) .def_readwrite("whisper_input_features", &PyGeneratorParams::py_whisper_input_features_) .def_readwrite("whisper_decoder_input_ids", &PyGeneratorParams::py_whisper_decoder_input_ids_) @@ -190,7 +187,7 @@ PYBIND11_MODULE(onnxruntime_genai, m) { pybind11::class_(m, "TokenizerStream") .def("decode", [](TokenizerStream& t, int32_t token) { return t.Decode(token); }); - pybind11::class_(m, "Tokenizer") + pybind11::class_>(m, "Tokenizer") .def(pybind11::init([](Model& model) { return model.CreateTokenizer(); })) .def("encode", &Tokenizer::Encode) .def("decode", [](const Tokenizer& t, pybind11::array_t tokens) { return t.Decode(ToSpan(tokens)); }) @@ -210,18 +207,11 @@ PYBIND11_MODULE(onnxruntime_genai, m) { }) .def("create_stream", [](const Tokenizer& t) { return t.CreateStream(); }); - pybind11::class_(m, "Model") + pybind11::class_>(m, "Model") .def(pybind11::init([](const std::string& config_path) { return CreateModel(GetOrtEnv(), config_path.c_str()); })) .def("generate", [](Model& model, PyGeneratorParams& params) { params.Prepare(); return Generate(model, params); }) - .def("generate_sequence", [](Model& model, pybind11::array_t input_ids, const pybind11::dict& search_options) { - PyGeneratorParams params{model}; - params.SetSearchOptions(search_options); - params.py_input_ids_ = input_ids; - params.Prepare(); - return Generate(model, params)[0]; - }) .def_property_readonly("device_type", [](const Model& s) { return s.device_type_; }); pybind11::class_(m, "Generator") @@ -229,10 +219,6 @@ PYBIND11_MODULE(onnxruntime_genai, m) { .def("is_done", &PyGenerator::IsDone) .def("compute_logits", &PyGenerator::ComputeLogits) .def("generate_next_token", &PyGenerator::GenerateNextToken) - .def("generate_next_token_top", &PyGenerator::GenerateNextToken_Top) - .def("generate_next_token_top_p", &PyGenerator::GenerateNextToken_TopP) - .def("generate_next_token_top_k", &PyGenerator::GenerateNextToken_TopK) - .def("generate_next_token_top_k_top_p", &PyGenerator::GenerateNextToken_TopK_TopP) .def("get_next_tokens", &PyGenerator::GetNextTokens) .def("get_sequence", &PyGenerator::GetSequence); diff --git a/src/search.cpp b/src/search.cpp index b7dd21e46..dd3389270 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -9,7 +9,7 @@ namespace Generators { Search_Cpu::Search_Cpu(const GeneratorParams& params) : Search{params}, - sequences_{params.input_ids, params.batch_size, params.search.num_beams, params_.search.max_length} { + sequences_{params.input_ids, params.batch_size, params.search.num_beams, params_->search.max_length} { auto batch_beam_size = params.BatchBeamSize(); sequence_lengths_buffer_ = AllocateArray(batch_beam_size, &sequence_lengths_); } @@ -25,8 +25,8 @@ GreedySearch_Cpu::GreedySearch_Cpu(const GeneratorParams& params) BeamSearch_Cpu::BeamSearch_Cpu(const GeneratorParams& params) : Search_Cpu(params) { - assert(params_.search.num_beams > 1); // If 1, use GreedySearch - beam_scorer_ = std::make_unique(params_); + assert(params_->search.num_beams > 1); // If 1, use GreedySearch + beam_scorer_ = std::make_unique(*params_); } BeamSearch_Cpu::~BeamSearch_Cpu() = default; @@ -58,16 +58,16 @@ void BeamSearch_Cpu::SelectTop() { // TODO(tianleiwu): use thread pool to parallel int offset = 0; int batch_beam_index = 0; - for (int i = 0; i < params_.batch_size; i++) { - for (int j = 0; j < params_.search.num_beams; j++, batch_beam_index++) { - for (int k = 0; k < params_.vocab_size; k++, offset++) { + for (int i = 0; i < params_->batch_size; i++) { + for (int j = 0; j < params_->search.num_beams; j++, batch_beam_index++) { + for (int k = 0; k < params_->vocab_size; k++, offset++) { next_token_scores_[offset] += beam_scores[batch_beam_index]; } } } // TODO: Write output scores? - unsigned const top_k = 2 * params_.search.num_beams; + const size_t top_k = 2 * params_->search.num_beams; struct ScoreIndex { float score; @@ -76,17 +76,17 @@ void BeamSearch_Cpu::SelectTop() { bool operator<(const ScoreIndex& s) const { return score < s.score; } }; - auto scores = std::make_unique(top_k * params_.batch_size); - auto indices = std::make_unique(top_k * params_.batch_size); - auto tokens = std::make_unique(top_k * params_.batch_size); + auto scores = std::make_unique(top_k * params_->batch_size); + auto indices = std::make_unique(top_k * params_->batch_size); + auto tokens = std::make_unique(top_k * params_->batch_size); - auto next_scores = std::span(scores.get(), top_k * params_.batch_size); - auto next_indices = std::span(indices.get(), top_k * params_.batch_size); - auto next_tokens = std::span(tokens.get(), top_k * params_.batch_size); + auto next_scores = std::span(scores.get(), top_k * params_->batch_size); + auto next_indices = std::span(indices.get(), top_k * params_->batch_size); + auto next_tokens = std::span(tokens.get(), top_k * params_->batch_size); - for (size_t batch_index = 0; batch_index < static_cast(params_.batch_size); batch_index++) { + for (size_t batch_index = 0; batch_index < static_cast(params_->batch_size); batch_index++) { std::priority_queue> queue; - auto token_scores_sub = next_token_scores_.subspan(batch_index * params_.search.num_beams * params_.vocab_size, params_.search.num_beams * params_.vocab_size); + auto token_scores_sub = next_token_scores_.subspan(batch_index * params_->search.num_beams * params_->vocab_size, static_cast(params_->search.num_beams) * params_->vocab_size); for (int i = 0; i < token_scores_sub.size(); i++) { queue.push({token_scores_sub[i], i}); } @@ -96,8 +96,8 @@ void BeamSearch_Cpu::SelectTop() { auto next_scores_sub = next_scores.subspan(top_k * batch_index, top_k); for (unsigned i = 0; i < top_k; i++) { auto v = queue.top(); - next_indices_sub[i] = v.index / params_.vocab_size; - next_tokens_sub[i] = v.index % params_.vocab_size; + next_indices_sub[i] = v.index / params_->vocab_size; + next_tokens_sub[i] = v.index % params_->vocab_size; next_scores_sub[i] = v.score; queue.pop(); } @@ -117,12 +117,12 @@ void BeamSearch_Cpu::SelectTop() { void GreedySearch_Cpu::SelectTop() { // next_tokens = torch.argmax(scores, dim=-1) - for (size_t batch_id = 0; batch_id < params_.batch_size; batch_id++) { + for (size_t batch_id = 0; batch_id < params_->batch_size; batch_id++) { if (PadIfAlreadyEOS(batch_id)) { continue; } - std::span const scores = next_token_scores_.subspan(batch_id * params_.vocab_size, params_.vocab_size); + std::span const scores = next_token_scores_.subspan(batch_id * params_->vocab_size, params_->vocab_size); auto const token = static_cast(std::distance(scores.begin(), std::max_element(scores.begin(), scores.end()))); SetNextToken(batch_id, token); } @@ -144,8 +144,8 @@ void SoftMax(std::span scores, float temperature) { } void GreedySearch_Cpu::SampleTopK(int k, float temperature) { - for (size_t batch_id = 0; batch_id < params_.batch_size; batch_id++) { - std::span const scores = next_token_scores_.subspan(batch_id * params_.vocab_size, params_.vocab_size); + for (size_t batch_id = 0; batch_id < params_->batch_size; batch_id++) { + std::span const scores = next_token_scores_.subspan(batch_id * params_->vocab_size, params_->vocab_size); SoftMax(scores, temperature); // Find the top K scores std::vector indices(scores.size()); @@ -160,11 +160,11 @@ void GreedySearch_Cpu::SampleTopK(int k, float temperature) { void GreedySearch_Cpu::SampleTopP(float p, float temperature) { std::uniform_real_distribution dis(0, p); - for (size_t batch_id = 0; batch_id < params_.batch_size; batch_id++) { + for (size_t batch_id = 0; batch_id < params_->batch_size; batch_id++) { if (PadIfAlreadyEOS(batch_id)) { continue; } - std::span const scores = next_token_scores_.subspan(batch_id * params_.vocab_size, params_.vocab_size); + std::span const scores = next_token_scores_.subspan(batch_id * params_->vocab_size, params_->vocab_size); SoftMax(scores, temperature); // Sort an array of indices into the scores std::vector indices(scores.size()); @@ -187,13 +187,13 @@ void GreedySearch_Cpu::SampleTopP(float p, float temperature) { AppendNextTokensToSequences(); } -void GreedySearch_Cpu::SampleTopPAndK(float p, int k, float temperature) { +void GreedySearch_Cpu::SampleTopKTopP(int k, float p, float temperature) { std::uniform_real_distribution dis(0, p); - for (size_t batch_id = 0; batch_id < params_.batch_size; batch_id++) { + for (size_t batch_id = 0; batch_id < params_->batch_size; batch_id++) { if (PadIfAlreadyEOS(batch_id)) { continue; } - std::span const scores = next_token_scores_.subspan(batch_id * params_.vocab_size, params_.vocab_size); + std::span const scores = next_token_scores_.subspan(batch_id * params_->vocab_size, params_->vocab_size); SoftMax(scores, temperature); // Find the top K scores std::vector indices(scores.size()); @@ -222,13 +222,13 @@ bool GreedySearch_Cpu::PadIfAlreadyEOS(size_t batch_id) { return false; } - next_tokens_[batch_id] = params_.pad_token_id; + next_tokens_[batch_id] = params_->pad_token_id; return true; } void GreedySearch_Cpu::SetNextToken(size_t batch_id, int32_t token) { next_tokens_[batch_id] = token; - if (token == params_.eos_token_id) { + if (token == params_->eos_token_id) { eos_seen_[batch_id] = true; if (--not_done_count_ == 0) { done_ = true; @@ -239,7 +239,7 @@ void GreedySearch_Cpu::SetNextToken(size_t batch_id, int32_t token) { void GreedySearch_Cpu::AppendNextTokensToSequences() { sequences_.AppendNextTokenToSequences(next_tokens_); - if (sequences_.GetSequenceLength() == params_.search.max_length) { + if (sequences_.GetSequenceLength() == params_->search.max_length) { done_ = true; } } @@ -247,7 +247,7 @@ void GreedySearch_Cpu::AppendNextTokensToSequences() { void BeamSearch_Cpu::AppendNextTokensToSequences() { sequences_.AppendNextTokenToSequences(beam_scorer_->GetNextIndicesCPU(), beam_scorer_->GetNextTokens()); - if (sequences_.GetSequenceLength() == params_.search.max_length) { + if (sequences_.GetSequenceLength() == params_->search.max_length) { done_ = true; } } @@ -257,8 +257,8 @@ void BeamSearch_Cpu::Finalize(size_t num_return_sequences, RoamingArray } std::span Search_Cpu::GetScores(int batch_beam_index) const { - assert(batch_beam_index >= 0 && batch_beam_index < params_.BatchBeamSize()); - return next_token_scores_.subspan(batch_beam_index * params_.vocab_size, params_.vocab_size); + assert(batch_beam_index >= 0 && batch_beam_index < params_->BatchBeamSize()); + return next_token_scores_.subspan(static_cast(batch_beam_index) * params_->vocab_size, params_->vocab_size); } void Search_Cpu::ApplyMinLength(int min_length) { @@ -266,10 +266,10 @@ void Search_Cpu::ApplyMinLength(int min_length) { return; } - const int batch_beam_size = params_.BatchBeamSize(); + const int batch_beam_size = params_->BatchBeamSize(); for (int i = 0; i < batch_beam_size; i++) { std::span const beam_token_scores = GetScores(i); - beam_token_scores[params_.eos_token_id] = std::numeric_limits::lowest(); + beam_token_scores[params_->eos_token_id] = std::numeric_limits::lowest(); } } @@ -277,7 +277,7 @@ void Search_Cpu::ApplyRepetitionPenalty(float penalty) { if (penalty == 1.0f) return; - const int batch_beam_size = params_.BatchBeamSize(); + const int batch_beam_size = params_->BatchBeamSize(); for (int i = 0; i < batch_beam_size; i++) { std::span const beam_token_scores = GetScores(i); std::span const sequence = sequences_.GetSequence(i); diff --git a/src/search.h b/src/search.h index abf373b51..5a52c11e2 100644 --- a/src/search.h +++ b/src/search.h @@ -6,7 +6,7 @@ namespace Generators { struct BeamSearchScorer; struct Search { - Search(const GeneratorParams& params) : params_{params} {} + Search(const GeneratorParams& params) : params_{params.shared_from_this()} {} virtual ~Search() = default; virtual RoamingArray GetNextTokens() = 0; @@ -24,13 +24,13 @@ struct Search { virtual void SelectTop() = 0; virtual void SampleTopP(float /*p*/, float /*temperature*/) { assert(false); } virtual void SampleTopK(int /*k*/, float /*temperature*/) { assert(false); } - virtual void SampleTopPAndK(float /*p*/, int /*k*/, float /*temperature*/) { assert(false); } + virtual void SampleTopKTopP(int /*k*/, float /*p*/, float /*temperature*/) { assert(false); } // Scoring features virtual void ApplyMinLength(int min_length) = 0; virtual void ApplyRepetitionPenalty(float penalty) = 0; - const GeneratorParams& params_; + std::shared_ptr params_; }; struct Search_Cpu : Search { @@ -69,7 +69,7 @@ struct GreedySearch_Cpu : Search_Cpu { void SelectTop() override; void SampleTopK(int k, float temperature) override; void SampleTopP(float p, float temperature) override; - void SampleTopPAndK(float /*p*/, int /*k*/, float /*temperature*/) override; + void SampleTopKTopP(int /*k*/, float /*p*/, float /*temperature*/) override; private: bool PadIfAlreadyEOS(size_t batch_id); @@ -81,7 +81,7 @@ struct GreedySearch_Cpu : Search_Cpu { std::span eos_seen_; // shape (batch_size) std::unique_ptr eos_seen_buffer_; - int not_done_count_{params_.batch_size}; // When zero, every batch entry is done (starts at batch_size_) + int not_done_count_{params_->batch_size}; // When zero, every batch entry is done (starts at batch_size_) std::random_device rd_; std::mt19937 gen_; diff --git a/src/search_cuda.cpp b/src/search_cuda.cpp index bc285cad0..aa6d85431 100644 --- a/src/search_cuda.cpp +++ b/src/search_cuda.cpp @@ -17,13 +17,13 @@ void OnCudaError(cudaError_t error) { Search_Cuda::Search_Cuda(const GeneratorParams& params) : Search{params}, - sequences_{params.input_ids, params.batch_size, params.search.num_beams, params_.search.max_length, params_.cuda_stream} { + sequences_{params.input_ids, params.batch_size, params.search.num_beams, params_->search.max_length, params_->cuda_stream} { auto batch_beam_size = params.BatchBeamSize(); sequence_lengths_buffer_ = std::make_unique(batch_beam_size); sequence_lengths_ = cpu_span(sequence_lengths_buffer_.get(), batch_beam_size); eos_meet_buffer_ = CudaMallocArray(batch_beam_size, &eos_meet_); - cudaMemsetAsync(eos_meet_.data(), 0, eos_meet_.size_bytes(), params_.cuda_stream); + cudaMemsetAsync(eos_meet_.data(), 0, eos_meet_.size_bytes(), params_->cuda_stream); done_cpu_ = CudaMallocHostArray(1); *done_cpu_ = false; @@ -32,26 +32,26 @@ Search_Cuda::Search_Cuda(const GeneratorParams& params) GreedySearch_Cuda::GreedySearch_Cuda(const GeneratorParams& params) : Search_Cuda{params} { next_tokens_buffer_ = CudaMallocArray(params.batch_size, &next_tokens_); - cudaMemsetAsync(next_tokens_.data(), 0, next_tokens_.size_bytes(), params_.cuda_stream); - samplingdata_ = std::make_unique(params_.batch_size, params_.vocab_size, params_.cuda_stream); + cudaMemsetAsync(next_tokens_.data(), 0, next_tokens_.size_bytes(), params_->cuda_stream); + samplingdata_ = std::make_unique(params_->batch_size, params_->vocab_size, params_->cuda_stream); } BeamSearch_Cuda::BeamSearch_Cuda(const GeneratorParams& params) : Search_Cuda{params} { - assert(params_.search.num_beams > 1); // If 1, use GreedySearch - auto batch_beam_size = params_.BatchBeamSize(); - beam_scorer_ = std::make_unique(params_); + assert(params_->search.num_beams > 1); // If 1, use GreedySearch + auto batch_beam_size = params_->BatchBeamSize(); + beam_scorer_ = std::make_unique(*params_); topk_next_tokens_ = CudaMallocArray(2 * batch_beam_size); topk_next_indices_ = CudaMallocArray(2 * batch_beam_size); topk_next_scores_ = CudaMallocArray(2 * batch_beam_size); constexpr size_t max_parts_of_vocab = 128; - size_t topk_buffer_size = batch_beam_size * (max_parts_of_vocab + 1) * params_.search.num_beams * 2 * 2; + size_t topk_buffer_size = batch_beam_size * (max_parts_of_vocab + 1) * params_->search.num_beams * 2 * 2; topk_buffer_ = CudaMallocArray(topk_buffer_size); static_assert(sizeof(float) == sizeof(int32_t)); // The topk_buffer assumes these match, fix for float16 - cudaMemsetAsync(topk_buffer_.get(), 0, topk_buffer_size * sizeof(float), params_.cuda_stream); + cudaMemsetAsync(topk_buffer_.get(), 0, topk_buffer_size * sizeof(float), params_->cuda_stream); } BeamSearch_Cuda::~BeamSearch_Cuda() = default; @@ -82,13 +82,13 @@ void BeamSearch_Cuda::SelectTop() { // Add beam score to next token scores. Corresponding python code is like: // next_token_scores = next_token_scores + beam_scores[:, None].expand_as(next_token_scores) cuda::LaunchAddProbsKernel(next_token_scores_.data(), beam_scores.data(), - params_.batch_size, params_.search.num_beams, params_.vocab_size, params_.cuda_stream); + params_->batch_size, params_->search.num_beams, params_->vocab_size, params_->cuda_stream); // TODO: Write output scores? - if (params_.search.num_beams <= 32) { + if (params_->search.num_beams <= 32) { constexpr size_t max_parts_of_vocab = 128; - size_t candidate_count = params_.BatchBeamSize() * 2 * params_.search.num_beams; + size_t candidate_count = params_->BatchBeamSize() * 2 * params_->search.num_beams; float* topk_tmp_buffer = topk_buffer_.get(); float* topk_scores_1st_stage = topk_tmp_buffer; int32_t* topk_tokens_1st_stage = reinterpret_cast(topk_scores_1st_stage + candidate_count * max_parts_of_vocab); @@ -96,10 +96,10 @@ void BeamSearch_Cuda::SelectTop() { int32_t* topk_tokens_2nd_stage = reinterpret_cast(topk_scores_2nd_stage + candidate_count); cuda::BeamSearchTopK(next_token_scores_.data(), - params_.batch_size, - params_.search.num_beams, - params_.vocab_size, - 2 * params_.search.num_beams, + params_->batch_size, + params_->search.num_beams, + params_->vocab_size, + 2 * params_->search.num_beams, topk_scores_1st_stage, topk_tokens_1st_stage, topk_scores_2nd_stage, @@ -107,13 +107,13 @@ void BeamSearch_Cuda::SelectTop() { topk_next_scores_.get(), topk_next_tokens_.get(), topk_next_indices_.get(), - params_.cuda_stream); + params_->cuda_stream); } else assert(false); - CudaCheck() == cudaStreamSynchronize(params_.cuda_stream); + CudaCheck() == cudaStreamSynchronize(params_->cuda_stream); - size_t size = params_.BatchBeamSize() * 2; + size_t size = params_->BatchBeamSize() * 2; std::span next_scores{topk_next_scores_.get(), size}; std::span next_tokens{topk_next_tokens_.get(), size}; std::span next_indices{topk_next_indices_.get(), size}; @@ -131,52 +131,52 @@ void BeamSearch_Cuda::SelectTop() { } void GreedySearch_Cuda::SelectTop() { - std::span scores = next_token_scores_.subspan(0, params_.batch_size * params_.vocab_size); - cuda::GetSample(samplingdata_.get(), params_.cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_.batch_size), - params_.batch_size, 1, 0.0, 1.0); + std::span scores = next_token_scores_.subspan(0, params_->batch_size * params_->vocab_size); + cuda::GetSample(samplingdata_.get(), params_->cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_->batch_size), + params_->batch_size, 1, 0.0, 1.0); CheckForEOS(); AppendNextTokensToSequences(); } void GreedySearch_Cuda::SampleTopP(float p, float temperature) { - std::span scores = next_token_scores_.subspan(0, params_.batch_size * params_.vocab_size); - cuda::GetSample(samplingdata_.get(), params_.cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_.batch_size), - params_.batch_size, -1, p, temperature); + std::span scores = next_token_scores_.subspan(0, params_->batch_size * params_->vocab_size); + cuda::GetSample(samplingdata_.get(), params_->cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_->batch_size), + params_->batch_size, -1, p, temperature); CheckForEOS(); AppendNextTokensToSequences(); } void GreedySearch_Cuda::SampleTopK(int k, float temperature) { - std::span scores = next_token_scores_.subspan(0, params_.batch_size * params_.vocab_size); - cuda::GetSample(samplingdata_.get(), params_.cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_.batch_size), - params_.batch_size, k, 0.0, temperature); + std::span scores = next_token_scores_.subspan(0, params_->batch_size * params_->vocab_size); + cuda::GetSample(samplingdata_.get(), params_->cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_->batch_size), + params_->batch_size, k, 0.0, temperature); CheckForEOS(); AppendNextTokensToSequences(); } -void GreedySearch_Cuda::SampleTopPAndK(float p, int k, float temperature) { - std::span scores = next_token_scores_.subspan(0, params_.batch_size * params_.vocab_size); - cuda::GetSample(samplingdata_.get(), params_.cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_.batch_size), - params_.batch_size, k, p, temperature); +void GreedySearch_Cuda::SampleTopKTopP(int k, float p, float temperature) { + std::span scores = next_token_scores_.subspan(0, params_->batch_size * params_->vocab_size); + cuda::GetSample(samplingdata_.get(), params_->cuda_stream, next_tokens_.data(), scores.data(), int(scores.size() / params_->batch_size), + params_->batch_size, k, p, temperature); CheckForEOS(); AppendNextTokensToSequences(); } void GreedySearch_Cuda::CheckForEOS() { assert(next_tokens_.size() == eos_meet_.size()); - cuda::Launch_CheckForEOS(next_tokens_.data(), static_cast(next_tokens_.size()), eos_meet_.data(), params_.eos_token_id, params_.pad_token_id, done_cpu_.get(), params_.cuda_stream); + cuda::Launch_CheckForEOS(next_tokens_.data(), static_cast(next_tokens_.size()), eos_meet_.data(), params_->eos_token_id, params_->pad_token_id, done_cpu_.get(), params_->cuda_stream); } void GreedySearch_Cuda::AppendNextTokensToSequences() { sequences_.AppendNextTokenToSequences(next_tokens_); - if (sequences_.GetSequenceLength() == params_.search.max_length) + if (sequences_.GetSequenceLength() == params_->search.max_length) *done_cpu_ = true; } bool BeamSearch_Cuda::IsDone() const { beam_scorer_->IsDone(); - return beam_scorer_->IsDoneLater() || sequences_.GetSequenceLength() == params_.search.max_length; + return beam_scorer_->IsDoneLater() || sequences_.GetSequenceLength() == params_->search.max_length; } void BeamSearch_Cuda::AppendNextTokensToSequences() { @@ -195,10 +195,10 @@ void GreedySearch::Finalize(size_t num_return_sequences, std::span outp // Copy the sequences to output std::span output{ output_sequences_->GetTensorMutableData(), shape_count}; - for (int batch_id = 0; batch_id < params_.batch_size; ++batch_id) { + for (int batch_id = 0; batch_id < params_->batch_size; ++batch_id) { auto batch_output = output.subspan( - static_cast(batch_id) * params_.max_length, - params_.max_length); + static_cast(batch_id) * params_->max_length, + params_->max_length); std::span sequence_source = sequences_.GetSequence(batch_id); std::copy(sequence_source, batch_output); } @@ -206,8 +206,8 @@ void GreedySearch::Finalize(size_t num_return_sequences, std::span outp #endif std::span Search_Cuda::GetScores(int batch_beam_index) { - assert(batch_beam_index >= 0 && batch_beam_index < params_.BatchBeamSize()); - return next_token_scores_.subspan(batch_beam_index * params_.vocab_size, params_.vocab_size); + assert(batch_beam_index >= 0 && batch_beam_index < params_->BatchBeamSize()); + return next_token_scores_.subspan(batch_beam_index * params_->vocab_size, params_->vocab_size); } std::span Search_Cuda::GetScores() { @@ -218,7 +218,7 @@ void Search_Cuda::ApplyMinLength(int min_length) { if (sequences_.GetSequenceLength() >= min_length) return; - cuda::LaunchSetScoreProcessor(GetScores().data(), params_.BatchBeamSize(), params_.vocab_size, params_.eos_token_id, std::numeric_limits::lowest(), params_.cuda_stream); + cuda::LaunchSetScoreProcessor(GetScores().data(), params_->BatchBeamSize(), params_->vocab_size, params_->eos_token_id, std::numeric_limits::lowest(), params_->cuda_stream); } void Search_Cuda::ApplyRepetitionPenalty(float penalty) { @@ -226,8 +226,8 @@ void Search_Cuda::ApplyRepetitionPenalty(float penalty) { return; cuda::LaunchRepetitionPenaltyProcessor(sequences_.GetSequences().data(), - GetScores().data(), params_.batch_size, params_.search.num_beams, params_.vocab_size, - params_.search.max_length, GetSequenceLength(), penalty, params_.cuda_stream); + GetScores().data(), params_->batch_size, params_->search.num_beams, params_->vocab_size, + params_->search.max_length, GetSequenceLength(), penalty, params_->cuda_stream); } } // namespace Generators \ No newline at end of file diff --git a/src/search_cuda.h b/src/search_cuda.h index 50628b9f3..11a5a428d 100644 --- a/src/search_cuda.h +++ b/src/search_cuda.h @@ -15,7 +15,7 @@ struct Search_Cuda : Search { RoamingArray GetSequence(int index) override { return sequences_.GetSequence(index); } bool IsDone() const { - cudaStreamSynchronize(params_.cuda_stream); + cudaStreamSynchronize(params_->cuda_stream); return *done_cpu_; } // TODO: Use an event void SetLogits(RoamingArray logits); @@ -51,7 +51,7 @@ struct GreedySearch_Cuda : Search_Cuda { void SelectTop() override; void SampleTopK(int k, float t) override; void SampleTopP(float p, float t) override; - void SampleTopPAndK(float p, int k, float t) override; + void SampleTopKTopP(int k, float p, float t) override; private: void CheckForEOS(); diff --git a/test/c_api_tests.cpp b/test/c_api_tests.cpp index ab5bfc169..3a04a8180 100644 --- a/test/c_api_tests.cpp +++ b/test/c_api_tests.cpp @@ -187,6 +187,7 @@ TEST(CAPITests, GreedySearchGptFp32CAPI) { CheckResult(OgaCreateGeneratorParams(model, ¶ms)); OgaGeneratorParamsPtr params_ptr{params}; CheckResult(OgaGeneratorParamsSetSearchNumber(params, "max_length", max_length)); + CheckResult(OgaGeneratorParamsSetSearchBool(params, "do_sample", false)); CheckResult(OgaGeneratorParamsSetInputIDs(params, input_ids.data(), input_ids.size(), sequence_length, batch_size)); OgaGenerator* generator; @@ -195,7 +196,7 @@ TEST(CAPITests, GreedySearchGptFp32CAPI) { while (!OgaGenerator_IsDone(generator)) { CheckResult(OgaGenerator_ComputeLogits(generator)); - CheckResult(OgaGenerator_GenerateNextToken_Top(generator)); + CheckResult(OgaGenerator_GenerateNextToken(generator)); } // Verify outputs match expected outputs @@ -221,3 +222,153 @@ TEST(CAPITests, GreedySearchGptFp32CAPI) { EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), max_length * sizeof(int32_t))); } } + +#if TEST_PHI2 +TEST(CAPITests, TopKCAPI) { + float top_k = 50; + float temp = 0.6f; + + OgaModel* model; + CheckResult(OgaCreateModel(MODEL_PATH "phi-2", &model)); + OgaModelPtr model_ptr{model}; + + OgaTokenizer* tokenizer; + CheckResult(OgaCreateTokenizer(model, &tokenizer)); + OgaTokenizerPtr tokenizer_ptr{tokenizer}; + + OgaSequences* input_sequences; + CheckResult(OgaCreateSequences(&input_sequences)); + OgaSequencesPtr sequences_ptr{input_sequences}; + + const char* input_strings[] = { + "This is a test.", + "Rats are awesome pets!", + "The quick brown fox jumps over the lazy dog.", + }; + + for (auto& string : input_strings) + CheckResult(OgaTokenizerEncode(tokenizer, string, input_sequences)); + + OgaGeneratorParams* params; + CheckResult(OgaCreateGeneratorParams(model, ¶ms)); + OgaGeneratorParamsPtr params_ptr{params}; + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "max_length", 40)); + CheckResult(OgaGeneratorParamsSetSearchBool(params, "do_sample", true)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "top_k", top_k)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "temperature", temp)); + CheckResult(OgaGeneratorParamsSetInputSequences(params, input_sequences)); + + OgaSequences* output_sequences; + CheckResult(OgaGenerate(model, params, &output_sequences)); + OgaSequencesPtr output_sequences_ptr{output_sequences}; + + // Decode The Batch + for (size_t i = 0; i < OgaSequencesCount(output_sequences); i++) { + std::span sequence{OgaSequencesGetSequenceData(output_sequences, i), OgaSequencesGetSequenceCount(output_sequences, i)}; + + const char* out_string; + CheckResult(OgaTokenizerDecode(tokenizer, sequence.data(), sequence.size(), &out_string)); + std::cout << "Decoded string:" << out_string << std::endl; + OgaDestroyString(out_string); + } +} + +TEST(CAPITests, TopPCAPI) { + float top_p = 0.6f; + float temp = 0.6f; + + OgaModel* model; + CheckResult(OgaCreateModel(MODEL_PATH "phi-2", &model)); + OgaModelPtr model_ptr{model}; + + OgaTokenizer* tokenizer; + CheckResult(OgaCreateTokenizer(model, &tokenizer)); + OgaTokenizerPtr tokenizer_ptr{tokenizer}; + + OgaSequences* input_sequences; + CheckResult(OgaCreateSequences(&input_sequences)); + OgaSequencesPtr sequences_ptr{input_sequences}; + + const char* input_strings[] = { + "This is a test.", + "Rats are awesome pets!", + "The quick brown fox jumps over the lazy dog.", + }; + + for (auto& string : input_strings) + CheckResult(OgaTokenizerEncode(tokenizer, string, input_sequences)); + + OgaGeneratorParams* params; + CheckResult(OgaCreateGeneratorParams(model, ¶ms)); + OgaGeneratorParamsPtr params_ptr{params}; + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "max_length", 40)); + CheckResult(OgaGeneratorParamsSetSearchBool(params, "do_sample", true)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "top_p", top_p)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "temperature", temp)); + CheckResult(OgaGeneratorParamsSetInputSequences(params, input_sequences)); + OgaSequences* output_sequences; + CheckResult(OgaGenerate(model, params, &output_sequences)); + OgaSequencesPtr output_sequences_ptr{output_sequences}; + + // Decode The Batch + for (size_t i = 0; i < OgaSequencesCount(output_sequences); i++) { + std::span sequence{OgaSequencesGetSequenceData(output_sequences, i), OgaSequencesGetSequenceCount(output_sequences, i)}; + + const char* out_string; + CheckResult(OgaTokenizerDecode(tokenizer, sequence.data(), sequence.size(), &out_string)); + std::cout << "Decoded string:" << out_string << std::endl; + OgaDestroyString(out_string); + } +} + +TEST(CAPITests, TopKTopPCAPI) { + float top_p = 0.6f; + int top_k = 50; + float temp = 0.6f; + + OgaModel* model; + CheckResult(OgaCreateModel(MODEL_PATH "phi-2", &model)); + OgaModelPtr model_ptr{model}; + + OgaTokenizer* tokenizer; + CheckResult(OgaCreateTokenizer(model, &tokenizer)); + OgaTokenizerPtr tokenizer_ptr{tokenizer}; + + OgaSequences* input_sequences; + CheckResult(OgaCreateSequences(&input_sequences)); + OgaSequencesPtr sequences_ptr{input_sequences}; + + const char* input_strings[] = { + "This is a test.", + "Rats are awesome pets!", + "The quick brown fox jumps over the lazy dog.", + }; + + for (auto& string : input_strings) + CheckResult(OgaTokenizerEncode(tokenizer, string, input_sequences)); + + OgaGeneratorParams* params; + CheckResult(OgaCreateGeneratorParams(model, ¶ms)); + OgaGeneratorParamsPtr params_ptr{params}; + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "max_length", 40)); + CheckResult(OgaGeneratorParamsSetSearchBool(params, "do_sample", true)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "top_k", top_k)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "top_p", top_p)); + CheckResult(OgaGeneratorParamsSetSearchNumber(params, "temperature", temp)); + CheckResult(OgaGeneratorParamsSetInputSequences(params, input_sequences)); + OgaSequences* output_sequences; + CheckResult(OgaGenerate(model, params, &output_sequences)); + OgaSequencesPtr output_sequences_ptr{output_sequences}; + + // Decode The Batch + for (size_t i = 0; i < OgaSequencesCount(output_sequences); i++) { + std::span sequence{OgaSequencesGetSequenceData(output_sequences, i), OgaSequencesGetSequenceCount(output_sequences, i)}; + + const char* out_string; + CheckResult(OgaTokenizerDecode(tokenizer, sequence.data(), sequence.size(), &out_string)); + std::cout << "Decoded string:" << out_string << std::endl; + OgaDestroyString(out_string); + } +} + +#endif // TEST_PHI2 diff --git a/test/csharp/TestOnnxRuntimeGenAIAPI.cs b/test/csharp/TestOnnxRuntimeGenAIAPI.cs index cd3ba82d1..2113ffdca 100644 --- a/test/csharp/TestOnnxRuntimeGenAIAPI.cs +++ b/test/csharp/TestOnnxRuntimeGenAIAPI.cs @@ -8,6 +8,7 @@ using Microsoft.ML.OnnxRuntimeGenAI; using System.Collections.Generic; using System.Linq; +using System.Reflection.Emit; namespace Microsoft.ML.OnnxRuntimeGenAI.Tests { @@ -20,6 +21,20 @@ public OnnxRuntimeGenAITests(ITestOutputHelper o) this.output = o; } + private class IgnoreOnModelAbsebceFact : FactAttribute + { + public IgnoreOnModelAbsebceFact() + { + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); + bool exists = System.IO.Directory.Exists(modelPath); + if (!System.IO.Directory.Exists(modelPath)) + { + // Skip this test on some machines since the model cannot be downloaded on those machines at runtime. + Skip = "Skipping this test since the model does not exist."; + } + } + } + [Fact(DisplayName = "TestGreedySearch")] public void TestGreedySearch() { @@ -49,7 +64,7 @@ public void TestGreedySearch() while (!generator.IsDone()) { generator.ComputeLogits(); - generator.GenerateNextTokenTop(); + generator.GenerateNextToken(); } for (ulong i = 0; i < batchSize; i++) @@ -72,10 +87,138 @@ public void TestGreedySearch() } } - [Fact(DisplayName = "TestTokenizerBatchEncodeDecode")] + [IgnoreOnModelAbsebceFact(DisplayName = "TestTopKSearch")] + public void TestTopKSearch() + { + int topK = 100; + float temp = 0.6f; + ulong maxLength = 20; + + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); + using (var model = new Model(modelPath)) + { + Assert.NotNull(model); + using (var tokenizer = new Tokenizer(model)) + { + Assert.NotNull(tokenizer); + + var strings = new string[] { + "This is a test.", + "Rats are awesome pets!", + "The quick brown fox jumps over the lazy dog." + }; + + var sequences = tokenizer.EncodeBatch(strings); + Assert.NotNull(sequences); + Assert.Equal((ulong)strings.Length, sequences.NumSequences); + + using GeneratorParams generatorParams = new GeneratorParams(model); + Assert.NotNull(generatorParams); + + generatorParams.SetInputSequences(sequences); + generatorParams.SetSearchOption("max_length", maxLength); + generatorParams.SetSearchOption("do_sample", true); + generatorParams.SetSearchOption("top_k", topK); + generatorParams.SetSearchOption("temperature", temp); + var outputSequences = model.Generate(generatorParams); + Assert.NotNull(outputSequences); + + var outputStrings = tokenizer.DecodeBatch(outputSequences); + Assert.NotNull(outputStrings); + } + } + } + + [IgnoreOnModelAbsebceFact(DisplayName = "TestTopPSearch")] + public void TestTopPSearch() + { + float topP = 0.6f; + float temp = 0.6f; + ulong maxLength = 20; + + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); + using (var model = new Model(modelPath)) + { + Assert.NotNull(model); + using (var tokenizer = new Tokenizer(model)) + { + Assert.NotNull(tokenizer); + + var strings = new string[] { + "This is a test.", + "Rats are awesome pets!", + "The quick brown fox jumps over the lazy dog." + }; + + var sequences = tokenizer.EncodeBatch(strings); + Assert.NotNull(sequences); + Assert.Equal((ulong)strings.Length, sequences.NumSequences); + + using GeneratorParams generatorParams = new GeneratorParams(model); + Assert.NotNull(generatorParams); + + generatorParams.SetInputSequences(sequences); + generatorParams.SetSearchOption("max_length", maxLength); + generatorParams.SetSearchOption("do_sample", true); + generatorParams.SetSearchOption("top_p", topP); + generatorParams.SetSearchOption("temperature", temp); + var outputSequences = model.Generate(generatorParams); + Assert.NotNull(outputSequences); + + var outputStrings = tokenizer.DecodeBatch(outputSequences); + Assert.NotNull(outputStrings); + } + } + } + + [IgnoreOnModelAbsebceFact(DisplayName = "TestTopKTopPSearch")] + public void TestTopKTopPSearch() + { + int topK = 100; + float topP = 0.6f; + float temp = 0.6f; + ulong maxLength = 20; + + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); + using (var model = new Model(modelPath)) + { + Assert.NotNull(model); + using (var tokenizer = new Tokenizer(model)) + { + Assert.NotNull(tokenizer); + + var strings = new string[] { + "This is a test.", + "Rats are awesome pets!", + "The quick brown fox jumps over the lazy dog." + }; + + var sequences = tokenizer.EncodeBatch(strings); + Assert.NotNull(sequences); + Assert.Equal((ulong)strings.Length, sequences.NumSequences); + + using GeneratorParams generatorParams = new GeneratorParams(model); + Assert.NotNull(generatorParams); + + generatorParams.SetInputSequences(sequences); + generatorParams.SetSearchOption("max_length", maxLength); + generatorParams.SetSearchOption("do_sample", true); + generatorParams.SetSearchOption("top_k", topK); + generatorParams.SetSearchOption("top_p", topP); + generatorParams.SetSearchOption("temperature", temp); + var outputSequences = model.Generate(generatorParams); + Assert.NotNull(outputSequences); + + var outputStrings = tokenizer.DecodeBatch(outputSequences); + Assert.NotNull(outputStrings); + } + } + } + + [IgnoreOnModelAbsebceFact(DisplayName = "TestTokenizerBatchEncodeDecode")] public void TestTokenizerBatchEncodeDecode() { - string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); using (var model = new Model(modelPath)) { Assert.NotNull(model); @@ -101,10 +244,10 @@ public void TestTokenizerBatchEncodeDecode() } } - [Fact(DisplayName = "TestTokenizerBatchEncodeSingleDecode")] + [IgnoreOnModelAbsebceFact(DisplayName = "TestTokenizerBatchEncodeSingleDecode")] public void TestTokenizerBatchEncodeSingleDecode() { - string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); using (var model = new Model(modelPath)) { Assert.NotNull(model); @@ -132,10 +275,10 @@ public void TestTokenizerBatchEncodeSingleDecode() } } - [Fact(DisplayName = "TestTokenizerBatchEncodeStreamDecode")] + [IgnoreOnModelAbsebceFact(DisplayName = "TestTokenizerBatchEncodeStreamDecode")] public void TestTokenizerBatchEncodeStreamDecode() { - string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); using (var model = new Model(modelPath)) { Assert.NotNull(model); @@ -168,10 +311,10 @@ public void TestTokenizerBatchEncodeStreamDecode() } } - [Fact(DisplayName = "TestTokenizerSingleEncodeDecode")] + [IgnoreOnModelAbsebceFact(DisplayName = "TestTokenizerSingleEncodeDecode")] public void TestTokenizerSingleEncodeDecode() { - string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "hf-internal-testing", "tiny-random-gpt2-fp32"); + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); using (var model = new Model(modelPath)) { Assert.NotNull(model); @@ -192,10 +335,10 @@ public void TestTokenizerSingleEncodeDecode() } } - [Fact(Skip = "Phi-2 is not available in the CI pipeline")] + [IgnoreOnModelAbsebceFact(DisplayName = "TestPhi2")] public void TestPhi2() { - string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "phi-2"); + string modelPath = Path.Combine(Directory.GetCurrentDirectory(), "test_models", "cpu", "phi-2"); using (var model = new Model(modelPath)) { Assert.NotNull(model); diff --git a/test/model_tests.cpp b/test/model_tests.cpp index 90655cfed..79c1d2c64 100644 --- a/test/model_tests.cpp +++ b/test/model_tests.cpp @@ -34,24 +34,24 @@ TEST(ModelTests, GreedySearchGptFp32) { auto model = Generators::CreateModel(*g_ort_env, MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp32"); - Generators::GeneratorParams params{*model}; - params.search.max_length = 10; - params.batch_size = static_cast(input_ids_shape[0]); - params.sequence_length = static_cast(input_ids_shape[1]); - params.input_ids = input_ids; + auto params = Generators::CreateGeneratorParams(*model); + params->search.max_length = 10; + params->batch_size = static_cast(input_ids_shape[0]); + params->sequence_length = static_cast(input_ids_shape[1]); + params->input_ids = input_ids; - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); while (!generator->IsDone()) { generator->ComputeLogits(); - generator->GenerateNextToken_Top(); + generator->GenerateNextToken(); } // Verify outputs match expected outputs - for (size_t i = 0; i < static_cast(params.batch_size); i++) { + for (size_t i = 0; i < static_cast(params->batch_size); i++) { auto sequence = generator->GetSequence(i).GetCPU(); - auto* expected_output_start = &expected_output[i * params.search.max_length]; - EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params.search.max_length * sizeof(int32_t))); + auto* expected_output_start = &expected_output[i * params->search.max_length]; + EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params->search.max_length * sizeof(int32_t))); } } @@ -74,16 +74,16 @@ TEST(ModelTests, BeamSearchGptFp32) { auto model = Generators::CreateModel(*g_ort_env, MODEL_PATH "hf-internal-testing/tiny-random-gpt2-fp32"); - Generators::GeneratorParams params{*model}; - params.batch_size = static_cast(input_ids_shape[0]); - params.sequence_length = static_cast(input_ids_shape[1]); - params.input_ids = input_ids; - params.search.max_length = 20; - params.search.length_penalty = 1.0f; - params.search.num_beams = 4; + auto params = Generators::CreateGeneratorParams(*model); + params->batch_size = static_cast(input_ids_shape[0]); + params->sequence_length = static_cast(input_ids_shape[1]); + params->input_ids = input_ids; + params->search.max_length = 20; + params->search.length_penalty = 1.0f; + params->search.num_beams = 4; - Generators::BeamSearch_Cpu search{params}; - auto state = model->CreateState(search.sequence_lengths_, params); + Generators::BeamSearch_Cpu search{*params}; + auto state = model->CreateState(search.sequence_lengths_, *params); while (!search.IsDone()) { search.SetLogits(state->Run(search.GetSequenceLength(), search.GetNextTokens(), search.GetNextIndices())); @@ -95,14 +95,14 @@ TEST(ModelTests, BeamSearchGptFp32) { search.SelectTop(); } - std::vector output_sequence(static_cast(search.params_.batch_size) * search.params_.search.max_length); + std::vector output_sequence(static_cast(search.params_->batch_size) * search.params_->search.max_length); search.Finalize(1, Generators::cpu_span{output_sequence}, {}); // Verify outputs match expected outputs - for (size_t i = 0; i < static_cast(search.params_.batch_size); i++) { - auto sequence = std::span(output_sequence.data() + search.params_.search.max_length * i, search.params_.search.max_length); - auto* expected_output_start = &expected_output[i * search.params_.search.max_length]; - EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params.search.max_length * sizeof(int32_t))); + for (size_t i = 0; i < static_cast(search.params_->batch_size); i++) { + auto sequence = std::span(output_sequence.data() + search.params_->search.max_length * i, search.params_->search.max_length); + auto* expected_output_start = &expected_output[i * search.params_->search.max_length]; + EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params->search.max_length * sizeof(int32_t))); } } @@ -118,25 +118,25 @@ void Test_GreedySearch_Gpt_Cuda(const char* model_path, const char* model_label) auto model = Generators::CreateModel(*g_ort_env, model_path); - Generators::GeneratorParams params{*model}; - params.batch_size = static_cast(input_ids_shape[0]); - params.sequence_length = static_cast(input_ids_shape[1]); - params.search.max_length = 10; - params.input_ids = input_ids; + auto params = Generators::CreateGeneratorParams(*model); + params->batch_size = static_cast(input_ids_shape[0]); + params->sequence_length = static_cast(input_ids_shape[1]); + params->search.max_length = 10; + params->input_ids = input_ids; - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); while (!generator->IsDone()) { generator->ComputeLogits(); - generator->GenerateNextToken_Top(); + generator->GenerateNextToken(); } // Verify outputs match expected outputs - for (int i = 0; i < params.batch_size; i++) { + for (int i = 0; i < params->batch_size; i++) { auto sequence_gpu = generator->GetSequence(i); auto sequence = sequence_gpu.GetCPU(); - auto* expected_output_start = &expected_output[i * params.search.max_length]; - EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params.search.max_length * sizeof(int32_t))); + auto* expected_output_start = &expected_output[i * params->search.max_length]; + EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params->search.max_length * sizeof(int32_t))); } } @@ -163,34 +163,34 @@ void Test_BeamSearch_Gpt_Cuda(const char* model_path, const char* model_label) { // (with separate_gpt2_decoder_for_init_run set to False as it is now set to True by default) auto model = Generators::CreateModel(*g_ort_env, model_path); - Generators::GeneratorParams params{*model}; - params.batch_size = static_cast(input_ids_shape[0]); - params.sequence_length = static_cast(input_ids_shape[1]); - params.input_ids = input_ids; - params.search.max_length = 20; - params.search.num_beams = 4; - params.search.length_penalty = 1.0f; + auto params = Generators::CreateGeneratorParams(*model); + params->batch_size = static_cast(input_ids_shape[0]); + params->sequence_length = static_cast(input_ids_shape[1]); + params->input_ids = input_ids; + params->search.max_length = 20; + params->search.num_beams = 4; + params->search.length_penalty = 1.0f; - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); while (!generator->IsDone()) { generator->ComputeLogits(); generator->GenerateNextToken(); } - size_t sequence_length = params.batch_size * params.search.max_length; + size_t sequence_length = params->batch_size * params->search.max_length; auto output_sequence_cuda = Generators::CudaMallocArray(sequence_length); auto output_sequence_cpu = std::make_unique(sequence_length); generator->search_->Finalize(1, Generators::gpu_span(output_sequence_cuda.get(), sequence_length), {}); - cudaMemcpyAsync(output_sequence_cpu.get(), output_sequence_cuda.get(), sequence_length * sizeof(int32_t), cudaMemcpyDeviceToHost, params.cuda_stream); - cudaStreamSynchronize(params.cuda_stream); + cudaMemcpyAsync(output_sequence_cpu.get(), output_sequence_cuda.get(), sequence_length * sizeof(int32_t), cudaMemcpyDeviceToHost, params->cuda_stream); + cudaStreamSynchronize(params->cuda_stream); // Verify outputs match expected outputs - for (int i = 0; i < params.batch_size; i++) { - auto sequence = std::span(output_sequence_cpu.get() + params.search.max_length * i, params.search.max_length); - auto* expected_output_start = &expected_output[i * params.search.max_length]; - EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params.search.max_length * sizeof(int32_t))); + for (int i = 0; i < params->batch_size; i++) { + auto sequence = std::span(output_sequence_cpu.get() + params->search.max_length * i, params->search.max_length); + auto* expected_output_start = &expected_output[i * params->search.max_length]; + EXPECT_TRUE(0 == std::memcmp(expected_output_start, sequence.data(), params->search.max_length * sizeof(int32_t))); } } @@ -216,17 +216,17 @@ Print all primes between 1 and n auto tokenizer = model->CreateTokenizer(); auto tokens = tokenizer->Encode(prompt); - Generators::GeneratorParams params{*model}; - params.batch_size = 1; - params.sequence_length = static_cast(tokens.size()); - params.input_ids = tokens; - params.search.max_length = 128; + auto params = Generators::CreateGeneratorParams(*model); + params->batch_size = 1; + params->sequence_length = static_cast(tokens.size()); + params->input_ids = tokens; + params->search.max_length = 128; // Generator version - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); while (!generator->IsDone()) { generator->ComputeLogits(); - generator->GenerateNextToken_Top(); + generator->GenerateNextToken(); } auto result = generator->GetSequence(0); @@ -254,14 +254,14 @@ Print all primes between 1 and n auto tokenizer = model->CreateTokenizer(); auto tokens = tokenizer->Encode(prompt); - Generators::GeneratorParams params{*model}; - params.batch_size = 1; - params.sequence_length = static_cast(tokens.size()); - params.input_ids = tokens; - params.search.max_length = 128; + auto params = Generators::CreateGeneratorParams(*model); + params->batch_size = 1; + params->sequence_length = static_cast(tokens.size()); + params->input_ids = tokens; + params->search.max_length = 128; // High level version - auto result = Generators::Generate(*model, params); + auto result = Generators::Generate(*model, *params); std::cout << tokenizer->Decode(result[0]) << "\r\n"; #else diff --git a/test/python/_test_utils.py b/test/python/_test_utils.py index 9dd7f571e..a314454ba 100644 --- a/test/python/_test_utils.py +++ b/test/python/_test_utils.py @@ -50,3 +50,34 @@ def run_subprocess( "Subprocess completed. Return code=" + str(completed_process.returncode) ) return completed_process + + +def download_models(download_path, device): + # python -m onnxruntime_genai.models.builder -m -p int4 -e cpu -o --extra_options num_hidden_layers=1 + model_names = { + "cpu": { + "phi-2": "microsoft/phi-2", + }, + "cuda": { + "phi-2": "microsoft/phi-2", + }, + } + for model_name, model_identifier in model_names[device].items(): + model_path = os.path.join(download_path, device, model_name) + if not os.path.exists(model_path): + command = [ + sys.executable, + "-m", + "onnxruntime_genai.models.builder", + "-m", + model_identifier, + "-p", + "int4", + "-e", + device, + "-o", + model_path, + "--extra_options", + "num_hidden_layers=1", + ] + run_subprocess(command).check_returncode() diff --git a/test/python/conftest.py b/test/python/conftest.py index a9751c566..08498d184 100644 --- a/test/python/conftest.py +++ b/test/python/conftest.py @@ -1,11 +1,11 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License +import functools import os import sys import pytest - from _test_utils import run_subprocess @@ -18,39 +18,44 @@ def pytest_addoption(parser): ) -def download_phi2(model_path): - # python -m onnxruntime_genai.models.builder -m microsoft/phi-2 -p int4 -e cpu -o model_path - device = "cpu" # FIXME: "cuda" if og.is_cuda_available() else "cpu" - command = [ - sys.executable, - "-m", - "onnxruntime_genai.models.builder", - "-m", - "microsoft/phi-2", - "-p", - "int4", - "-e", - device, - "-o", - model_path, - ] - run_subprocess(command).check_returncode() +def get_path_for_model_and_device(data_path, model_name, device): + return os.path.join(data_path, device, model_name) + + +@pytest.fixture +def phi2_for(request): + return functools.partial( + get_path_for_model_and_device, + request.config.getoption("--test_models"), + "phi-2", + ) + + +@pytest.fixture +def gemma_for(request): + return functools.partial( + get_path_for_model_and_device, + request.config.getoption("--test_models"), + "gemma", + ) + + +@pytest.fixture +def llama_for(request): + return functools.partial( + get_path_for_model_and_device, + request.config.getoption("--test_models"), + "llama", + ) + + +@pytest.fixture +def path_for_model(request): + return functools.partial( + get_path_for_model_and_device, request.config.getoption("--test_models") + ) @pytest.fixture def test_data_path(request): - def _get_model_path(model_name=None): - if not model_name: - return request.config.getoption("--test_models") - - if model_name == "phi-2": - model_path = os.path.join( - request.config.getoption("--test_models"), "phi-2" - ) - if not os.path.exists(model_path): - download_phi2(model_path) - return model_path - else: - raise ValueError(f"Unknown model name: {model_name}") - - return _get_model_path + return request.config.getoption("--test_models") diff --git a/test/python/test_onnxruntime_genai.py b/test/python/test_onnxruntime_genai.py index 85e7b2713..41d615e51 100644 --- a/test/python/test_onnxruntime_genai.py +++ b/test/python/test_onnxruntime_genai.py @@ -6,9 +6,11 @@ import os import pathlib import sys +import sysconfig from typing import Union -from _test_utils import run_subprocess +import onnxruntime_genai as og +from _test_utils import download_models, run_subprocess logging.basicConfig( format="%(asctime)s %(name)s [%(levelname)s] - %(message)s", level=logging.DEBUG @@ -42,8 +44,7 @@ def run_onnxruntime_genai_e2e_tests( ): log.debug("Running: ONNX Runtime GenAI E2E Tests") - log.debug("Running: Phi-2") - command = [sys.executable, "test_onnxruntime_genai_phi2.py"] + command = [sys.executable, "test_onnxruntime_genai_e2e.py"] run_subprocess(command, cwd=cwd, log=log).check_returncode() @@ -73,11 +74,22 @@ def main(): log.info("Running onnxruntime-genai tests pipeline") - run_onnxruntime_genai_api_tests( - os.path.abspath(args.cwd), log, os.path.abspath(args.test_models) - ) - - if args.e2e: + if not args.e2e: + if not ( + sysconfig.get_platform().endswith("arm64") or sys.version_info.minor < 8 + ): + download_models(os.path.abspath(args.test_models), "cpu") + if og.is_cuda_available(): + download_models( + os.path.abspath(args.test_models), + "cuda", + ) + + run_onnxruntime_genai_api_tests( + os.path.abspath(args.cwd), log, os.path.abspath(args.test_models) + ) + + else: run_onnxruntime_genai_e2e_tests(os.path.abspath(args.cwd), log) return 0 diff --git a/test/python/test_onnxruntime_genai_api.py b/test/python/test_onnxruntime_genai_api.py index 6346beba2..695fb8802 100644 --- a/test/python/test_onnxruntime_genai_api.py +++ b/test/python/test_onnxruntime_genai_api.py @@ -11,18 +11,20 @@ import pytest -# FIXME: CUDA device does not work on the CI pipeline because the pipeline uses different cuda versions for -# onnxruntime-genai and onnxruntime. This introduces incompatibility. -# Once this works, cuda converted models need to be added (ex: tiny-random-gpt2-fp16-cuda) @pytest.mark.parametrize( "relative_model_path", - [ - Path("hf-internal-testing") / "tiny-random-gpt2-fp32", - Path("hf-internal-testing") / "tiny-random-gpt2-fp32", - ], + ( + [ + Path("hf-internal-testing") / "tiny-random-gpt2-fp32", + Path("hf-internal-testing") / "tiny-random-gpt2-fp32-cuda", + Path("hf-internal-testing") / "tiny-random-gpt2-fp16-cuda", + ] + if og.is_cuda_available() + else [Path("hf-internal-testing") / "tiny-random-gpt2-fp32"] + ), ) def test_greedy_search(test_data_path, relative_model_path): - model_path = os.fspath(Path(test_data_path()) / relative_model_path) + model_path = os.fspath(Path(test_data_path) / relative_model_path) model = og.Model(model_path) @@ -30,14 +32,14 @@ def test_greedy_search(test_data_path, relative_model_path): search_params.input_ids = np.array( [[0, 0, 0, 52], [0, 0, 195, 731]], dtype=np.int32 ) - search_params.set_search_options({"max_length": 10}) + search_params.set_search_options({"do_sample": False, "max_length": 10}) input_ids_shape = [2, 4] batch_size = input_ids_shape[0] generator = og.Generator(model, search_params) while not generator.is_done(): generator.compute_logits() - generator.generate_next_token_top() + generator.generate_next_token() expected_sequence = np.array( [ @@ -60,9 +62,12 @@ def test_greedy_search(test_data_path, relative_model_path): sysconfig.get_platform().endswith("arm64") or sys.version_info.minor < 8, reason="Python 3.8 is required for downloading models.", ) +@pytest.mark.parametrize( + "device", ["cpu", "cuda"] if og.is_cuda_available() else ["cpu"] +) @pytest.mark.parametrize("batch", [True, False]) -def test_tokenizer_encode_decode(test_data_path, batch): - model_path = os.fspath(Path(test_data_path("phi-2"))) +def test_tokenizer_encode_decode(device, phi2_for, batch): + model_path = phi2_for(device) model = og.Model(model_path) tokenizer = og.Tokenizer(model) @@ -84,13 +89,15 @@ def test_tokenizer_encode_decode(test_data_path, batch): assert prompt == decoded_string +@pytest.mark.skipif( + sysconfig.get_platform().endswith("arm64") or sys.version_info.minor < 8, + reason="Python 3.8 is required for downloading models.", +) @pytest.mark.parametrize( - "relative_model_path", [Path("hf-internal-testing") / "tiny-random-gpt2-fp32"] + "device", ["cpu", "cuda"] if og.is_cuda_available() else ["cpu"] ) -def test_tokenizer_stream(test_data_path, relative_model_path): - model_path = os.fspath(Path(test_data_path()) / relative_model_path) - - model = og.Model(model_path) +def test_tokenizer_stream(device, phi2_for): + model = og.Model(phi2_for(device)) tokenizer = og.Tokenizer(model) tokenizer_stream = tokenizer.create_stream() @@ -115,11 +122,11 @@ def test_tokenizer_stream(test_data_path, relative_model_path): sysconfig.get_platform().endswith("arm64") or sys.version_info.minor < 8, reason="Python 3.8 is required for downloading models.", ) -@pytest.mark.parametrize("relative_model_path", [Path("phi-2")]) -def test_batching(test_data_path, relative_model_path): - model_path = os.fspath(Path(test_data_path()) / relative_model_path) - - model = og.Model(model_path) +@pytest.mark.parametrize( + "device", ["cpu", "cuda"] if og.is_cuda_available() else ["cpu"] +) +def test_batching(device, phi2_for): + model = og.Model(phi2_for(device)) tokenizer = og.Tokenizer(model) prompts = [ diff --git a/test/python/test_onnxruntime_genai_phi2.py b/test/python/test_onnxruntime_genai_e2e.py similarity index 65% rename from test/python/test_onnxruntime_genai_phi2.py rename to test/python/test_onnxruntime_genai_e2e.py index e2a996a37..cc6f9dde2 100644 --- a/test/python/test_onnxruntime_genai_phi2.py +++ b/test/python/test_onnxruntime_genai_e2e.py @@ -6,18 +6,19 @@ import tempfile import onnxruntime_genai as og - from _test_utils import run_subprocess -def download_model(download_path: str | bytes | os.PathLike, device: str): +def download_model( + download_path: str | bytes | os.PathLike, device: str, model_identifier: str +): # python -m onnxruntime_genai.models.builder -m microsoft/phi-2 -p int4 -e cpu -o download_path command = [ sys.executable, "-m", "onnxruntime_genai.models.builder", "-m", - "microsoft/phi-2", + model_identifier, "-p", "int4", "-e", @@ -28,8 +29,8 @@ def download_model(download_path: str | bytes | os.PathLike, device: str): run_subprocess(command).check_returncode() -def run_model(model_path: str | bytes | os.PathLike, device: og.DeviceType): - model = og.Model(model_path, device) +def run_model(model_path: str | bytes | os.PathLike): + model = og.Model(model_path) tokenizer = og.Tokenizer(model) prompts = [ @@ -41,7 +42,7 @@ def run_model(model_path: str | bytes | os.PathLike, device: og.DeviceType): sequences = tokenizer.encode_batch(prompts) params = og.GeneratorParams(model) params.set_search_options({"max_length": 200}) - params.input_ids=sequences + params.input_ids = sequences output_sequences = model.generate(params) output = tokenizer.decode_batch(output_sequences) @@ -49,9 +50,8 @@ def run_model(model_path: str | bytes | os.PathLike, device: og.DeviceType): if __name__ == "__main__": - with tempfile.TemporaryDirectory() as temp_dir: - device = "cpu" # FIXME: "cuda" if og.is_cuda_available() else "cpu" - download_model(temp_dir, device) - run_model( - temp_dir, og.DeviceType.CPU if device == "cpu" else og.DeviceType.CUDA - ) + for model_name in ["microsoft/phi-2"]: + with tempfile.TemporaryDirectory() as temp_dir: + device = "cuda" if og.is_cuda_available() else "cpu" + download_model(temp_dir, device, model_name) + run_model(temp_dir) diff --git a/test/sampling_benchmark.cpp b/test/sampling_benchmark.cpp index 0fcb138e6..6190e2507 100644 --- a/test/sampling_benchmark.cpp +++ b/test/sampling_benchmark.cpp @@ -24,24 +24,24 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPCpu) { int vocab_size = 32000; // vocab size of llama int batch_size = 1; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; - std::unique_ptr logits_cpu(new float[vocab_size * batch_size]); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; + std::vector logits_cpu(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(1, 25); double total_time = 0.0; int num_iter = 1000; for (int i = 0; i < num_iter; i++) { - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); int num_large = dist(engine); - CreateRandomLogits(logits_cpu.get(), num_large, vocab_size, batch_size, engine); - generator->search_->SetLogits(Generators::cpu_span(logits_cpu.get(), vocab_size * batch_size)); + CreateRandomLogits(logits_cpu.data(), num_large, vocab_size, batch_size, engine); + generator->search_->SetLogits(Generators::cpu_span(logits_cpu.data(), vocab_size * batch_size)); auto start = std::chrono::high_resolution_clock::now(); generator->search_->SampleTopP(0.95f, 1.0f); auto stop = std::chrono::high_resolution_clock::now(); @@ -59,14 +59,14 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopKCpu) { int batch_size = 1; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; - std::unique_ptr logits_cpu(new float[vocab_size * batch_size]); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; + std::vector logits_cpu(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(5, 25); @@ -74,9 +74,9 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopKCpu) { int num_iter = 1000; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - CreateRandomLogits(logits_cpu.get(), num_large, vocab_size, batch_size, engine); - generator->search_->SetLogits(Generators::cpu_span(logits_cpu.get(), vocab_size * batch_size)); + auto generator = Generators::CreateGenerator(*model, *params); + CreateRandomLogits(logits_cpu.data(), num_large, vocab_size, batch_size, engine); + generator->search_->SetLogits(Generators::cpu_span(logits_cpu.data(), vocab_size * batch_size)); auto start = std::chrono::high_resolution_clock::now(); generator->search_->SampleTopK(k, 1.0f); @@ -97,14 +97,14 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPAndKCpu) { float p = 0.95f; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; - std::unique_ptr logits_cpu(new float[vocab_size * batch_size]); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; + std::vector logits_cpu(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(5, 25); @@ -112,12 +112,12 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPAndKCpu) { int num_iter = 1000; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - CreateRandomLogits(logits_cpu.get(), num_large, vocab_size, batch_size, engine); - generator->search_->SetLogits(Generators::cpu_span(logits_cpu.get(), vocab_size * batch_size)); + auto generator = Generators::CreateGenerator(*model, *params); + CreateRandomLogits(logits_cpu.data(), num_large, vocab_size, batch_size, engine); + generator->search_->SetLogits(Generators::cpu_span(logits_cpu.data(), vocab_size * batch_size)); auto start = std::chrono::high_resolution_clock::now(); - generator->search_->SampleTopPAndK(p, k, 1.0f); + generator->search_->SampleTopKTopP(k, p, 1.0f); auto stop = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(stop - start); @@ -136,14 +136,14 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPCuda) { int vocab_size = 32000; // vocab size of llama int batch_size = 1; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; - float* cpu_logits = new float[vocab_size * batch_size]; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; + std::vector cpu_logits(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(1, 25); @@ -153,13 +153,13 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPCuda) { int num_iter = 1000; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpy(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); + auto generator = Generators::CreateGenerator(*model, *params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpy(cpu_logits.data(), logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); auto start = std::chrono::high_resolution_clock::now(); generator->search_->SampleTopP(0.95f, 1.0f); auto stop = std::chrono::high_resolution_clock::now(); @@ -167,7 +167,7 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPCuda) { total_time += duration.count(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); } double average_time = total_time / double(num_iter); std::cout << "Average time taken by TopP CUDA: " @@ -183,16 +183,16 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopKCuda) { int batch_size = 1; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocArray(vocab_size * batch_size); - float* cpu_logits = new float[vocab_size * batch_size]; + std::vector cpu_logits(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(1, 25); @@ -200,12 +200,12 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopKCuda) { int num_iter = 1000; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpy(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); + auto generator = Generators::CreateGenerator(*model, *params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpy(cpu_logits.data(), logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); auto start = std::chrono::high_resolution_clock::now(); generator->search_->SampleTopK(k, 1.0f); auto stop = std::chrono::high_resolution_clock::now(); @@ -227,38 +227,38 @@ TEST(Benchmarks, BenchmarkRandomizedSamplingTopPAndKCuda) { float p = 0.95f; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocArray(vocab_size * batch_size); - float* cpu_logits = new float[vocab_size * batch_size]; + std::vector cpu_logits(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(1, 25); double total_time = 0.0; int num_iter = 1000; for (int i = 0; i < num_iter; i++) { - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); int num_large = dist(engine); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpy(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpy(cpu_logits.data(), logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); auto start = std::chrono::high_resolution_clock::now(); - generator->search_->SampleTopPAndK(p, k, 1.0f); + generator->search_->SampleTopKTopP(k, p, 1.0f); auto stop = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(stop - start); total_time += duration.count(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); } double average_time = total_time / double(num_iter); std::cout << "Average time taken by TopP+K: " @@ -273,13 +273,13 @@ TEST(Benchmarks, BenchmarkRandomizedSelectTopCuda) { int vocab_size = 32000; // vocab size of llama int batch_size = 12; std::vector input_ids{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Needs to match batch_size - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocArray(vocab_size * batch_size); std::vector cpu_logits(vocab_size * batch_size); @@ -290,12 +290,12 @@ TEST(Benchmarks, BenchmarkRandomizedSelectTopCuda) { int num_iter = 1000; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); cudaMemcpy(cpu_logits.data(), logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); auto start = std::chrono::high_resolution_clock::now(); generator->search_->SelectTop(); auto stop = std::chrono::high_resolution_clock::now(); diff --git a/test/sampling_tests.cpp b/test/sampling_tests.cpp index 96264f015..239c71ab6 100644 --- a/test/sampling_tests.cpp +++ b/test/sampling_tests.cpp @@ -25,18 +25,21 @@ TEST(SamplingTests, BatchedSamplingTopPCpu) { 0.1f, 0.1f, 0.1f, 0.1f, 0.6f}; int vocab_size = 5; int batch_size = 4; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; - auto generator = Generators::CreateGenerator(*model, params); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample=true; + params->search.top_p=0.25f; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; + auto generator = Generators::CreateGenerator(*model, *params); auto logits_span = Generators::cpu_span(logits_cpu); generator->search_->SetLogits(logits_span); + generator->computed_logits_ = true; // Verify outputs match expected outputs - generator->search_->SampleTopP(0.25f, 1.0f); + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); EXPECT_TRUE(0 == std::memcmp(output_span.data(), next_tokens.data(), expected_output.size() * sizeof(int32_t))); } @@ -50,20 +53,22 @@ TEST(SamplingTests, BatchedSamplingTopKCpu) { 1.25f, 0.25f, 1.5f, 0.25f, 2.0f}; int vocab_size = 5; int batch_size = 4; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; - auto generator = Generators::CreateGenerator(*model, params); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = 2; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; + auto generator = Generators::CreateGenerator(*model, *params); auto logits_copy = logits_cpu; generator->search_->SetLogits(Generators::cpu_span(logits_copy)); + generator->computed_logits_ = true; // Verify outputs match expected outputs - int k = 2; - generator->search_->SampleTopK(k, 1.0); + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -81,20 +86,22 @@ TEST(SamplingTests, BatchedSamplingTopPAndKCpu) { 1.25f, 0.25f, 1.5f, 0.25f, 2.0f}; int vocab_size = 5; int batch_size = 4; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; - auto generator = Generators::CreateGenerator(*model, params); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = 2; + params->search.top_p = 0.25f; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; + auto generator = Generators::CreateGenerator(*model, *params); auto logits_copy = logits_cpu; generator->search_->SetLogits(Generators::cpu_span(logits_copy)); + generator->computed_logits_ = true; // Verify outputs match expected outputs - float p = 0.25f; - int k = 2; - generator->search_->SampleTopPAndK(p, k, 1.0); + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -125,25 +132,28 @@ TEST(SamplingTests, RandomizedSamplingTopPCpu) { int vocab_size = 32000; // vocab size of llama int batch_size = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_p = 0.95f; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; std::vector logits_cpu(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); std::uniform_int_distribution<> dist(1, 25); int num_iter = 100; for (int i = 0; i < num_iter; i++) { - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); int num_large = dist(engine); CreateRandomLogits(logits_cpu.data(), num_large, vocab_size, batch_size, engine); auto logits_copy = logits_cpu; generator->search_->SetLogits(Generators::cpu_span(logits_copy)); - generator->search_->SampleTopP(0.95f, 1.0f); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { @@ -160,13 +170,15 @@ TEST(SamplingTests, RandomizedSamplingTopKCpu) { int batch_size = 5; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = k; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; std::vector logits_cpu(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); @@ -174,11 +186,12 @@ TEST(SamplingTests, RandomizedSamplingTopKCpu) { int num_iter = 100; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); CreateRandomLogits(logits_cpu.data(), num_large, vocab_size, batch_size, engine); auto logits_copy=logits_cpu; generator->search_->SetLogits(Generators::cpu_span(logits_copy)); - generator->search_->SampleTopK(k, 1.0f); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { @@ -196,13 +209,16 @@ TEST(SamplingTests, RandomizedSamplingTopPAndKCpu) { float p = 0.95f; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CPU; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = k; + params->search.top_p = p; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CPU; std::vector logits_cpu(vocab_size * batch_size); std::random_device rd; std::mt19937 engine(rd()); @@ -210,11 +226,12 @@ TEST(SamplingTests, RandomizedSamplingTopPAndKCpu) { int num_iter = 100; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); + auto generator = Generators::CreateGenerator(*model, *params); CreateRandomLogits(logits_cpu.data(), num_large, vocab_size, batch_size, engine); auto logits_copy = logits_cpu; generator->search_->SetLogits(Generators::cpu_span(logits_copy)); - generator->search_->SampleTopPAndK(p, k, 1.0f); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { @@ -240,19 +257,22 @@ TEST(SamplingTests, BatchedSamplingTopPCuda) { auto logits_gpu = Generators::CudaMallocArray(logits_cpu.size()); int vocab_size = 5; int batch_size = 4; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; - cudaMemcpyAsync(logits_gpu.get(), logits_cpu.data(), logits_cpu.size() * sizeof(float), cudaMemcpyHostToDevice, params.cuda_stream); - cudaStreamSynchronize(params.cuda_stream); - auto generator = Generators::CreateGenerator(*model, params); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_p = 0.25f; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; + cudaMemcpyAsync(logits_gpu.get(), logits_cpu.data(), logits_cpu.size() * sizeof(float), cudaMemcpyHostToDevice, params->cuda_stream); + cudaStreamSynchronize(params->cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), logits_cpu.size())); + generator->computed_logits_ = true; // Verify outputs match expected outputs - generator->search_->SampleTopP(0.25f, 1.0f); + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); EXPECT_TRUE(0 == std::memcmp(output_span.data(), next_tokens.data(), expected_output.size() * sizeof(int32_t))); } @@ -267,20 +287,22 @@ TEST(SamplingTests, BatchedSamplingTopKCuda) { auto logits_gpu = Generators::CudaMallocArray(logits_cpu.size()); int vocab_size = 5; int batch_size = 4; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; - cudaMemcpyAsync(logits_gpu.get(), logits_cpu.data(), logits_cpu.size() * sizeof(float), cudaMemcpyHostToDevice, params.cuda_stream); - cudaStreamSynchronize(params.cuda_stream); - auto generator = Generators::CreateGenerator(*model, params); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = 2; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; + cudaMemcpyAsync(logits_gpu.get(), logits_cpu.data(), logits_cpu.size() * sizeof(float), cudaMemcpyHostToDevice, params->cuda_stream); + cudaStreamSynchronize(params->cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), logits_cpu.size())); + generator->computed_logits_ = true; // Verify outputs match expected outputs - int k = 2; - generator->search_->SampleTopK(k, 1.0); + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -299,21 +321,23 @@ TEST(SamplingTests, BatchedSamplingTopPAndKCuda) { auto logits_gpu = Generators::CudaMallocArray(logits_cpu.size()); int vocab_size = 5; int batch_size = 4; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; - cudaMemcpyAsync(logits_gpu.get(), logits_cpu.data(), logits_cpu.size() * sizeof(float), cudaMemcpyHostToDevice, params.cuda_stream); - cudaStreamSynchronize(params.cuda_stream); - auto generator = Generators::CreateGenerator(*model, params); + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = 2; + params->search.top_p = 0.25f; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; + cudaMemcpyAsync(logits_gpu.get(), logits_cpu.data(), logits_cpu.size() * sizeof(float), cudaMemcpyHostToDevice, params->cuda_stream); + cudaStreamSynchronize(params->cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), logits_cpu.size())); + generator->computed_logits_ = true; // Verify outputs match expected outputs - float p = 0.25f; - int k = 2; - generator->search_->SampleTopPAndK(p, k, 1.0); + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -327,13 +351,15 @@ TEST(SamplingTests, RandomizedSamplingTopPCuda) { int vocab_size = 32000; // vocab size of llama int batch_size = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_p = 0.95f; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocHostArray(vocab_size * batch_size); float* cpu_logits = new float[vocab_size * batch_size]; @@ -343,14 +369,15 @@ TEST(SamplingTests, RandomizedSamplingTopPCuda) { int num_iter = 100; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params.cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params->cuda_stream); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - generator->search_->SampleTopP(0.95f, 1.0f); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -366,13 +393,15 @@ TEST(SamplingTests, RandomizedSamplingTopKCuda) { int batch_size = 5; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = k; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocHostArray(vocab_size * batch_size); float* cpu_logits = new float[vocab_size * batch_size]; @@ -382,14 +411,15 @@ TEST(SamplingTests, RandomizedSamplingTopKCuda) { int num_iter = 100; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params.cuda_stream); - auto generator = Generators::CreateGenerator(*model, params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params->cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - generator->search_->SampleTopK(k, 1.0f); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -406,13 +436,16 @@ TEST(SamplingTests, RandomizedSamplingTopPAndKCuda) { float p = 0.95f; int k = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->search.do_sample = true; + params->search.top_k = k; + params->search.top_p = p; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocHostArray(vocab_size * batch_size); float* cpu_logits = new float[vocab_size * batch_size]; @@ -422,14 +455,15 @@ TEST(SamplingTests, RandomizedSamplingTopPAndKCuda) { int num_iter = 100; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - auto generator = Generators::CreateGenerator(*model, params); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params.cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params->cuda_stream); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - generator->search_->SampleTopPAndK(p, k, 1.0f); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { auto next_token = next_tokens[b]; @@ -444,13 +478,13 @@ TEST(SamplingTests, RandomizedSamplingSelectTopCuda) { int vocab_size = 32000; // vocab size of llama int batch_size = 5; std::vector input_ids{0, 1, 2, 3, 4}; - Generators::GeneratorParams params = Generators::GeneratorParams{}; - params.search.max_length = 10; - params.batch_size = batch_size; - params.sequence_length = 1; - params.vocab_size = vocab_size; - params.input_ids = input_ids; - params.device_type = Generators::DeviceType::CUDA; + auto params = Generators::CreateGeneratorParams(); + params->search.max_length = 10; + params->batch_size = batch_size; + params->sequence_length = 1; + params->vocab_size = vocab_size; + params->input_ids = input_ids; + params->device_type = Generators::DeviceType::CUDA; auto logits_gpu = Generators::CudaMallocArray(vocab_size * batch_size); auto indices_buffer = Generators::CudaMallocHostArray(vocab_size * batch_size); float* cpu_logits = new float[vocab_size * batch_size]; @@ -460,14 +494,15 @@ TEST(SamplingTests, RandomizedSamplingSelectTopCuda) { int num_iter = 100; for (int i = 0; i < num_iter; i++) { int num_large = dist(engine); - LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params.cuda_stream); - LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params.cuda_stream); - cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params.cuda_stream); - auto generator = Generators::CreateGenerator(*model, params); + LaunchGeometricDecayKernel(logits_gpu.get(), vocab_size, batch_size, num_large, 20.0f, params->cuda_stream); + LaunchFisherYatesKernel(logits_gpu.get(), indices_buffer.get(), vocab_size, batch_size, params->cuda_stream); + cudaMemcpyAsync(cpu_logits, logits_gpu.get(), vocab_size * batch_size * sizeof(float), cudaMemcpyDeviceToHost, params->cuda_stream); + auto generator = Generators::CreateGenerator(*model, *params); generator->search_->SetLogits(Generators::gpu_span(logits_gpu.get(), vocab_size * batch_size)); - generator->search_->SelectTop(); + generator->computed_logits_ = true; + generator->GenerateNextToken(); auto next_tokens = generator->search_->GetNextTokens().GetCPU(); - cudaStreamSynchronize(params.cuda_stream); + cudaStreamSynchronize(params->cuda_stream); // Verify outputs match expected outputs for (int b = 0; b < batch_size; b++) { float max_score = *std::max_element(cpu_logits + vocab_size * b, cpu_logits + vocab_size * (b + 1)); From 7d965afe87dcf3a84f7a033a6b526669cf132b6d Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Mon, 25 Mar 2024 13:13:07 -0400 Subject: [PATCH 22/23] Cjian/rc4 (#228) Co-authored-by: kunal-vaishnavi <115581922+kunal-vaishnavi@users.noreply.github.com> Co-authored-by: Ryan Hill <38674843+RyanUnderhill@users.noreply.github.com> Co-authored-by: Baiju Meswani Co-authored-by: aciddelgado <139922440+aciddelgado@users.noreply.github.com> --- .github/workflows/linux-cpu-arm64-build.yml | 4 +- .github/workflows/linux-cpu-x64-build.yml | 6 +- .github/workflows/linux-gpu-x64-build.yml | 4 +- .github/workflows/mac-cpu-arm64-build.yml | 9 +- .github/workflows/win-cpu-arm64-build.yml | 17 ++-- .github/workflows/win-cpu-x64-build.yml | 17 ++-- .github/workflows/win-gpu-x64-build.yml | 15 +-- .pipelines/codeql.yaml | 6 +- .../stages/jobs/py-linux-packaging-job.yml | 1 - .../stages/jobs/steps/capi-linux-step.yml | 2 +- .../stages/jobs/steps/capi-win-step.yml | 21 ++--- .../jobs/steps/compliant-and-cleanup-step.yml | 2 +- ...nt-governance-component-detection-step.yml | 6 +- .../stages/jobs/steps/nuget-win-step.yml | 14 +-- .../stages/jobs/steps/utils/capi-archive.yml | 12 +-- .../stages/jobs/steps/utils/download-ort.yml | 16 ++-- CMakeLists.txt | 20 +--- CMakePresets.json | 4 +- cmake/cxx_standard.cmake | 12 +++ .../presets/CMakeLinuxClangConfigPresets.json | 16 ++-- .../CMakeLinuxDefaultConfigPresets.json | 2 + cmake/presets/CMakeLinuxGccConfigPresets.json | 32 +++---- cmake/presets/CMakeMacOSBuildPresets.json | 24 +++++ cmake/presets/CMakeMacOSConfigPresets.json | 91 +++++++++++++++++++ cmake/presets/CMakeWinBuildPresets.json | 18 ++-- cmake/presets/CMakeWinConfigPresets.json | 40 ++++---- src/json.cpp | 2 +- src/python/CMakeLists.txt | 20 +--- src/tokenizer/CMakeLists.txt | 19 +--- test/CMakeLists.txt | 15 +-- 30 files changed, 275 insertions(+), 192 deletions(-) create mode 100644 cmake/cxx_standard.cmake create mode 100644 cmake/presets/CMakeMacOSBuildPresets.json create mode 100644 cmake/presets/CMakeMacOSConfigPresets.json diff --git a/.github/workflows/linux-cpu-arm64-build.yml b/.github/workflows/linux-cpu-arm64-build.yml index ec31c67ab..5018bdbb6 100644 --- a/.github/workflows/linux-cpu-arm64-build.yml +++ b/.github/workflows/linux-cpu-arm64-build.yml @@ -56,10 +56,10 @@ jobs: run: | docker run --rm \ --volume $GITHUB_WORKSPACE:/onnxruntime_src \ - -w /onnxruntime_src ort_genai_linux_arm64_gha bash -c "ls -l /onnxruntime_src/build/gcc_cpu/release/test/" + -w /onnxruntime_src ort_genai_linux_arm64_gha bash -c "ls -l /onnxruntime_src/build/cpu/test/" - name: Docker -- Run tests run: | docker run --rm \ --volume $GITHUB_WORKSPACE:/onnxruntime_src \ - -w /onnxruntime_src ort_genai_linux_arm64_gha bash -c "/onnxruntime_src/build/gcc_cpu/release/test/unit_tests" + -w /onnxruntime_src ort_genai_linux_arm64_gha bash -c "/onnxruntime_src/build/cpu/test/unit_tests" diff --git a/.github/workflows/linux-cpu-x64-build.yml b/.github/workflows/linux-cpu-x64-build.yml index fe5c92ad5..da202e19c 100644 --- a/.github/workflows/linux-cpu-x64-build.yml +++ b/.github/workflows/linux-cpu-x64-build.yml @@ -39,7 +39,7 @@ jobs: - name: Install the python wheel and test dependencies run: | - python3 -m pip install build/gcc_cpu/release/wheel/onnxruntime_genai*.whl + python3 -m pip install build/cpu/wheel/onnxruntime_genai*.whl python3 -m pip install -r test/python/requirements-nightly-cpu.txt --user - name: Get HuggingFace Token @@ -59,9 +59,9 @@ jobs: if: always() continue-on-error: true run: | - ls -l ${{ github.workspace }}/build/gcc_cpu/release + ls -l ${{ github.workspace }}/build/cpu - name: Run tests run: | set -e -x - ./build/gcc_cpu/release/test/unit_tests + ./build/cpu/test/unit_tests diff --git a/.github/workflows/linux-gpu-x64-build.yml b/.github/workflows/linux-gpu-x64-build.yml index 3a08b8b05..c4e4c372a 100644 --- a/.github/workflows/linux-gpu-x64-build.yml +++ b/.github/workflows/linux-gpu-x64-build.yml @@ -63,7 +63,7 @@ jobs: --gpus all \ --rm \ --volume $GITHUB_WORKSPACE:/onnxruntime_src \ - -w /onnxruntime_src ort_genai_linux_gpu_gha bash -c "python3 -m pip install /onnxruntime_src/build/gcc_cuda/release/wheel/onnxruntime_genai*.whl --user && python3 -m pip install -r test/python/requirements.txt --user && python3 test/python/test_onnxruntime_genai.py --cwd test/python --test_models test/test_models" + -w /onnxruntime_src ort_genai_linux_gpu_gha bash -c "python3 -m pip install /onnxruntime_src/build/cuda/wheel/onnxruntime_genai*.whl --user && python3 -m pip install -r test/python/requirements.txt --user && python3 test/python/test_onnxruntime_genai.py --cwd test/python --test_models test/test_models" - name: Docker -- Run tests run: | @@ -72,4 +72,4 @@ jobs: --gpus all \ --rm \ --volume $GITHUB_WORKSPACE:/onnxruntime_src \ - -w /onnxruntime_src ort_genai_linux_gpu_gha bash -c "/onnxruntime_src/build/gcc_cuda/release/test/unit_tests" + -w /onnxruntime_src ort_genai_linux_gpu_gha bash -c "/onnxruntime_src/build/cuda/test/unit_tests" diff --git a/.github/workflows/mac-cpu-arm64-build.yml b/.github/workflows/mac-cpu-arm64-build.yml index 448709252..d757370f4 100644 --- a/.github/workflows/mac-cpu-arm64-build.yml +++ b/.github/workflows/mac-cpu-arm64-build.yml @@ -33,13 +33,14 @@ jobs: run: | mv ${{ env.ort_dir }} ort - - name: Build with CMake and Clang + - name: Configure CMake run: | + cmake --preset macos_cpu_release + - name: Build with CMake run: | - cmake -G "Ninja" -B build -S . -DCMAKE_BUILD_TYPE=Release -DUSE_CUDA=OFF - cmake --build build --config Release --parallel - continue-on-error: true + cmake --build --preset macos_cpu_release --parallel + continue-on-error: false - name: Verify Build Artifacts if: always() diff --git a/.github/workflows/win-cpu-arm64-build.yml b/.github/workflows/win-cpu-arm64-build.yml index 4b43c5bae..7c64ba8ff 100644 --- a/.github/workflows/win-cpu-arm64-build.yml +++ b/.github/workflows/win-cpu-arm64-build.yml @@ -14,7 +14,7 @@ env: ort_dir: "onnxruntime-win-arm64-1.17.1" ort_zip: "$(ort_dir).zip" ort_url: "https://github.com/microsoft/onnxruntime/releases/download/v1.17.1/$(ort_zip)" - cmake_build_dir: 'build/release/cpu_default' + binaryDir: 'build/cpu' jobs: windows-cpu-arm64-build: @@ -45,14 +45,17 @@ jobs: run: | Rename-Item -Path $env:ort_dir -NewName ort - - name: Build with CMake + - name: Configure CMake run: | cmake --preset windows_arm64_cpu_release + + - name: Build with CMake + run: | cmake --build --preset windows_arm64_cpu_release --parallel - name: Install the Python Wheel and Test Dependencies run: | - python -m pip install (Get-ChildItem ("$env:cmake_build_dir\wheel\*.whl")) + python -m pip install (Get-ChildItem ("$env:binaryDir\wheel\*.whl")) python -m pip install -r test\python\requirements.txt - name: Run the Python Tests @@ -62,15 +65,15 @@ jobs: - name: Build the C# API and Run the C# Tests run: | cd test\csharp - dotnet test /p:NativeBuildOutputDir="$env:GITHUB_WORKSPACE\$env:cmake_build_dir\Release" + dotnet test /p:NativeBuildOutputDir="$env:GITHUB_WORKSPACE\$env:binaryDir\Release" - name: Verify Build Artifacts if: always() continue-on-error: true run: | - Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:cmake_build_dir -Recurse - Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:cmake_build_dir\test -Recurse + Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:binaryDir -Recurse + Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:binaryDir\test -Recurse - name: Run tests run: | - .\build\release\cpu_default\test\Release\unit_tests.exe \ No newline at end of file + .\build\cpu\test\Release\unit_tests.exe \ No newline at end of file diff --git a/.github/workflows/win-cpu-x64-build.yml b/.github/workflows/win-cpu-x64-build.yml index cfe792005..f13f3c2c8 100644 --- a/.github/workflows/win-cpu-x64-build.yml +++ b/.github/workflows/win-cpu-x64-build.yml @@ -14,7 +14,7 @@ env: ort_dir: "onnxruntime-win-x64-1.17.1" ort_zip: "$(ort_dir).zip" ort_url: "https://github.com/microsoft/onnxruntime/releases/download/v1.17.1/$(ort_zip)" - cmake_build_dir: 'build/release/cpu_default' + binaryDir: 'build/cpu' jobs: windows-cpu-x64-build: @@ -52,14 +52,17 @@ jobs: with: languages: 'cpp' - - name: Build with CMake + - name: Configure CMake run: | cmake --preset windows_x64_cpu_release + + - name: Build with CMake + run: | cmake --build --preset windows_x64_cpu_release --parallel - name: Install the python wheel and test dependencies run: | - python -m pip install (Get-ChildItem ("$env:cmake_build_dir\wheel\*.whl")) + python -m pip install (Get-ChildItem ("$env:binaryDir\wheel\*.whl")) python -m pip install -r test\python\requirements-nightly-cpu.txt - name: Get HuggingFace Token @@ -76,18 +79,18 @@ jobs: - name: Build the C# API and Run the C# Tests run: | cd test\csharp - dotnet test /p:NativeBuildOutputDir="$env:GITHUB_WORKSPACE\$env:cmake_build_dir\Release" + dotnet test /p:NativeBuildOutputDir="$env:GITHUB_WORKSPACE\$env:binaryDir\Release" - name: Verify Build Artifacts if: always() continue-on-error: true run: | - Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:cmake_build_dir -Recurse - Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:cmake_build_dir\test -Recurse + Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:binaryDir -Recurse + Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:binaryDir\test -Recurse - name: Run tests run: | - .\build\release\cpu_default\test\Release\unit_tests.exe + .\build\cpu\test\Release\unit_tests.exe - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/win-gpu-x64-build.yml b/.github/workflows/win-gpu-x64-build.yml index 48afb21d4..a3f1d338b 100644 --- a/.github/workflows/win-gpu-x64-build.yml +++ b/.github/workflows/win-gpu-x64-build.yml @@ -14,7 +14,7 @@ env: cuda_dir: "${{ github.workspace }}\\cuda_sdk" cuda_version: "11.8" CUDA_PATH: ${{ github.workspace }}\\cuda_sdk\\v11.8 - cmake_build_dir: 'build/release/cuda_default' + binaryDir: 'build/cuda' jobs: @@ -47,9 +47,12 @@ jobs: run: | Rename-Item -Path $env:ort_dir -NewName ort - - name: Build with CMake + - name: Configure CMake run: | cmake --preset windows_x64_cuda_release -T cuda=${{ env.cuda_dir }}\\v${{ env.cuda_version }} -DTEST_PHI2=False + + - name: Build with CMake + run: | cmake --build --preset windows_x64_cuda_release --parallel - name: Add CUDA to PATH @@ -58,7 +61,7 @@ jobs: - name: Install the Python Wheel and Test Dependencies run: | - python -m pip install (Get-ChildItem ("$env:cmake_build_dir\wheel\*.whl")) + python -m pip install (Get-ChildItem ("$env:binaryDir\wheel\*.whl")) python -m pip install -r test\python\requirements-nightly-cpu.txt - name: Get HuggingFace Token @@ -75,17 +78,17 @@ jobs: - name: Build the C# API and Run the C# Tests run: | cd test\csharp - dotnet test /p:Configuration=release /p:NativeBuildOutputDir="$env:GITHUB_WORKSPACE\$env:cmake_build_dir\Release" + dotnet test /p:Configuration=release /p:NativeBuildOutputDir="$env:GITHUB_WORKSPACE\$env:binaryDir\Release" - name: Verify Build Artifacts if: always() continue-on-error: true run: | - Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:cmake_build_dir -Recurse + Get-ChildItem -Path $env:GITHUB_WORKSPACE\$env:binaryDir -Recurse - name: Prepend CUDA to PATH and Run tests run: | $env:PATH = "${{ env.cuda_dir }}\\v${{ env.cuda_version }}\\bin;" + $env:PATH echo "Current PATH variable is: $env:PATH" - .\build\release\cuda_default\test\Release\unit_tests.exe \ No newline at end of file + .\build\cuda\test\Release\unit_tests.exe \ No newline at end of file diff --git a/.pipelines/codeql.yaml b/.pipelines/codeql.yaml index 40bf7a418..a2bc08934 100644 --- a/.pipelines/codeql.yaml +++ b/.pipelines/codeql.yaml @@ -41,7 +41,7 @@ stages: - task: onebranch.pipeline.tsaoptions@1 displayName: 'OneBranch TSAOptions' inputs: - tsaConfigFilePath: '$(Build.SourcesDirectory)\.config\tsaoptions.json' + tsaConfigFilePath: '$(Build.Repository.LocalPath)\.config\tsaoptions.json' appendSourceBranchName: false - task: CredScan@3 displayName: 🔍 Run CredScan @@ -49,7 +49,7 @@ stages: - task: PoliCheck@2 inputs: targetType: 'F' - targetArgument: '$(Build.SourcesDirectory)' + targetArgument: '$(Build.Repository.LocalPath)' - task: SdtReport@2 displayName: 📃 Create Security Analysis Report @@ -65,4 +65,4 @@ stages: continueOnError: true inputs: GdnPublishTsaOnboard: true - GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\.config\tsaoptions.json' \ No newline at end of file + GdnPublishTsaConfigFile: '$(Build.Repository.LocalPath)\.config\tsaoptions.json' \ No newline at end of file diff --git a/.pipelines/stages/jobs/py-linux-packaging-job.yml b/.pipelines/stages/jobs/py-linux-packaging-job.yml index 32ccc858b..b7c35d6a5 100644 --- a/.pipelines/stages/jobs/py-linux-packaging-job.yml +++ b/.pipelines/stages/jobs/py-linux-packaging-job.yml @@ -59,7 +59,6 @@ jobs: - template: steps/capi-linux-step.yml parameters: target: 'python' - genai_src: '$(Build.SourcesDirectory)/onnxruntime-genai' - template: steps/compliant-and-cleanup-step.yml diff --git a/.pipelines/stages/jobs/steps/capi-linux-step.yml b/.pipelines/stages/jobs/steps/capi-linux-step.yml index 6fa0f3c92..03f76feb1 100644 --- a/.pipelines/stages/jobs/steps/capi-linux-step.yml +++ b/.pipelines/stages/jobs/steps/capi-linux-step.yml @@ -93,7 +93,7 @@ steps: - task: BinSkim@4 displayName: 'Run BinSkim' inputs: - AnalyzeTargetGlob: '$(Build.Repository.LocalPath)/**/*.pyd' + AnalyzeTargetGlob: '$(Build.Repository.LocalPath)/build/**/*cpython*.so' continueOnError: true - bash: | diff --git a/.pipelines/stages/jobs/steps/capi-win-step.yml b/.pipelines/stages/jobs/steps/capi-win-step.yml index b28230a7e..aebc4cd13 100644 --- a/.pipelines/stages/jobs/steps/capi-win-step.yml +++ b/.pipelines/stages/jobs/steps/capi-win-step.yml @@ -10,12 +10,13 @@ steps: condition: or( eq (variables['ep'], ''), eq (variables['arch'], '')) - checkout: self + path: onnxruntime-genai clean: true submodules: recursive - task: onebranch.pipeline.tsaoptions@1 displayName: 'OneBranch TSAOptions' inputs: - tsaConfigFilePath: '$(Build.SourcesDirectory)\.config\tsaoptions.json' + tsaConfigFilePath: '$(Build.Repository.LocalPath)\.config\tsaoptions.json' appendSourceBranchName: false - template: utils/set-nightly-build-option-variable.yml @@ -31,30 +32,29 @@ steps: - template: utils/download-ort.yml parameters: archiveType: 'zip' - genai_src: '$(Build.SourcesDirectory)' - powershell: | - azcopy.exe cp --recursive "https://lotusscus.blob.core.windows.net/models/cuda_sdk/v$(cuda_version)" '$(Build.SourcesDirectory)\cuda_sdk' + azcopy.exe cp --recursive "https://lotusscus.blob.core.windows.net/models/cuda_sdk/v$(cuda_version)" 'cuda_sdk' displayName: 'Download CUDA' condition: eq(variables['ep'], 'cuda') - workingDirectory: '$(Build.SourcesDirectory)' + workingDirectory: '$(Build.Repository.LocalPath)' - powershell: | - cmake --preset windows_$(arch)_$(ep)_release -T cuda='$(Build.SourcesDirectory)\cuda_sdk\v$(cuda_version)' + cmake --preset windows_$(arch)_$(ep)_release -T cuda='$(Build.Repository.LocalPath)\cuda_sdk\v$(cuda_version)' displayName: 'Configure CMake C API with CUDA' condition: eq(variables['ep'], 'cuda') - workingDirectory: '$(Build.SourcesDirectory)' + workingDirectory: '$(Build.Repository.LocalPath)' - powershell: | cmake --preset windows_$(arch)_$(ep)_release displayName: 'Configure CMake C API without CUDA' condition: ne(variables['ep'], 'cuda') - workingDirectory: '$(Build.SourcesDirectory)' + workingDirectory: '$(Build.Repository.LocalPath)' - powershell: | cmake --build --preset windows_$(arch)_$(ep)_release --parallel --target ${{ parameters.target }} displayName: 'Build C API' - workingDirectory: '$(Build.SourcesDirectory)' + workingDirectory: '$(Build.Repository.LocalPath)' - ${{ if eq(parameters.target, 'onnxruntime-genai') }}: - template: compliant/win-esrp-dll-step.yml @@ -66,12 +66,11 @@ steps: - task: BinSkim@4 displayName: 'Run BinSkim' inputs: - AnalyzeTargetGlob: '$(Build.SourcesDirectory)\**\*genai.dll' + AnalyzeTargetGlob: '$(Build.Repository.LocalPath)\**\*genai.dll' continueOnError: true - template: utils/capi-archive.yml parameters: - genai_src: '$(Build.SourcesDirectory)' archiveType: zip - task: PublishBuildArtifacts@1 @@ -84,7 +83,7 @@ steps: - task: BinSkim@4 displayName: 'Run BinSkim' inputs: - AnalyzeTargetGlob: '$(Build.SourcesDirectory)\**\*.pyd' + AnalyzeTargetGlob: '$(Build.Repository.LocalPath)\**\*.pyd' continueOnError: true - template: compliant/win-esrp-dll-step.yml diff --git a/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml b/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml index fb8a420af..aa2f7b547 100644 --- a/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml +++ b/.pipelines/stages/jobs/steps/compliant-and-cleanup-step.yml @@ -10,7 +10,7 @@ steps: condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) inputs: GdnPublishTsaOnboard: false - GdnPublishTsaConfigFile: '$(Build.sourcesDirectory)\.config\tsaoptions.json' + GdnPublishTsaConfigFile: '$(Build.Repository.LocalPath)\.config\tsaoptions.json' continueOnError: true - template: compliant/component-governance-component-detection-step.yml diff --git a/.pipelines/stages/jobs/steps/compliant/component-governance-component-detection-step.yml b/.pipelines/stages/jobs/steps/compliant/component-governance-component-detection-step.yml index f1418e75b..d090cb15b 100644 --- a/.pipelines/stages/jobs/steps/compliant/component-governance-component-detection-step.yml +++ b/.pipelines/stages/jobs/steps/compliant/component-governance-component-detection-step.yml @@ -18,8 +18,4 @@ steps: condition: or(or(and(eq('${{parameters.condition}}', 'ci_only'), and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Scheduled'))), and(eq('${{parameters.condition}}', 'always'), always())), - and(eq('${{parameters.condition}}', 'succeeded'), succeeded())) - inputs: - # ignore dmlc-core tracker for its CI, which is not used in onnxruntime build - # ignore unit tests in emscripten. emscripten unit tests are not used in onnxruntime build - ignoreDirectories: '$(Build.SourcesDirectory)/cmake/external/emsdk/upstream/emscripten/tests' + and(eq('${{parameters.condition}}', 'succeeded'), succeeded())) \ No newline at end of file diff --git a/.pipelines/stages/jobs/steps/nuget-win-step.yml b/.pipelines/stages/jobs/steps/nuget-win-step.yml index 214635822..f2447be06 100644 --- a/.pipelines/stages/jobs/steps/nuget-win-step.yml +++ b/.pipelines/stages/jobs/steps/nuget-win-step.yml @@ -2,17 +2,17 @@ steps: - powershell: | dotnet build Microsoft.ML.OnnxRuntimeGenAI.csproj -p:Configuration="$(buildConfig)" -p:NativeBuildOutputDir="$(buildDir)\$(buildConfig)" displayName: 'Build CSharp' - workingDirectory: '$(Build.SourcesDirectory)\src\csharp' + workingDirectory: '$(Build.Repository.LocalPath)\src\csharp' - task: BinSkim@4 displayName: 'Run BinSkim' inputs: - AnalyzeTargetGlob: '$(Build.SourcesDirectory)\src\csharp\**\*.dll' + AnalyzeTargetGlob: '$(Build.Repository.LocalPath)\src\csharp\**\*.dll' continueOnError: true - template: compliant/win-esrp-dll-step.yml parameters: - FolderPath: '$(Build.SourcesDirectory)\src\csharp\bin\Release\' + FolderPath: '$(Build.Repository.LocalPath)\src\csharp\bin\Release\' DisplayName: 'ESRP - Sign C# dlls' Pattern: '*OnnxRuntimeGenAI*.dll' - powershell: | @@ -28,18 +28,18 @@ steps: -Prop version=$VERSION ` -Prop configuration=$(buildConfig) displayName: 'Nuget Packaging' - workingDirectory: '$(Build.SourcesDirectory)\nuget' + workingDirectory: '$(Build.Repository.LocalPath)\nuget' - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse + Get-ChildItem -Path $(Build.Repository.LocalPath) -Recurse displayName: 'List all files in the repo for' - powershell: | - Get-ChildItem -Path $(Build.SourcesDirectory) -Recurse + Get-ChildItem -Path $(Build.Repository.LocalPath) -Recurse - task: CopyFiles@2 displayName: 'Copy Nuget to: $(Build.ArtifactStagingDirectory)' inputs: - SourceFolder: '$(Build.SourcesDirectory)\nuget' + SourceFolder: '$(Build.Repository.LocalPath)\nuget' Contents: '*.nupkg' TargetFolder: '$(Build.ArtifactStagingDirectory)\nuget' diff --git a/.pipelines/stages/jobs/steps/utils/capi-archive.yml b/.pipelines/stages/jobs/steps/utils/capi-archive.yml index e9c62a9af..1395b31f7 100644 --- a/.pipelines/stages/jobs/steps/utils/capi-archive.yml +++ b/.pipelines/stages/jobs/steps/utils/capi-archive.yml @@ -1,6 +1,4 @@ parameters: -- name: genai_src - type: string - name: archiveType type: string steps: @@ -13,14 +11,14 @@ steps: - task: CopyFiles@2 displayName: 'Copy Onnxruntime C API library files to ArtifactStagingDirectory' inputs: - SourceFolder: '${{ parameters.genai_src }}/ort/lib' + SourceFolder: '$(Build.Repository.LocalPath)/ort/lib' TargetFolder: '$(Build.ArtifactStagingDirectory)\$(artifactName)\lib' - ${{ if eq(parameters.archiveType, 'tar') }}: - task: CopyFiles@2 displayName: 'Copy Onnxruntime C API dll files to ArtifactStagingDirectory' inputs: - SourceFolder: '${{ parameters.genai_src }}/$(buildDir)' + SourceFolder: '$(Build.Repository.LocalPath)/$(buildDir)' Contents: | onnxruntime-genai.so TargetFolder: '$(Build.ArtifactStagingDirectory)\$(artifactName)\lib' @@ -28,7 +26,7 @@ steps: - task: CopyFiles@2 displayName: 'Copy Onnxruntime C API dll files to ArtifactStagingDirectory' inputs: - SourceFolder: '${{ parameters.genai_src }}\$(buildDir)\Release' + SourceFolder: '$(Build.Repository.LocalPath)\$(buildDir)\Release' Contents: | onnxruntime-genai.dll onnxruntime-genai.lib @@ -38,7 +36,7 @@ steps: - task: CopyFiles@2 displayName: 'Copy GenAi C API header to ArtifactStagingDirectory' inputs: - SourceFolder: '${{ parameters.genai_src }}/src' + SourceFolder: '$(Build.Repository.LocalPath)/src' Contents: | ort_genai_c.h TargetFolder: '$(Build.ArtifactStagingDirectory)/$(artifactName)/include' @@ -46,7 +44,7 @@ steps: - task: CopyFiles@2 displayName: 'Copy other files to ArtifactStagingDirectory' inputs: - SourceFolder: '${{ parameters.genai_src }}' + SourceFolder: '$(Build.Repository.LocalPath)' Contents: | VERSION_INFO LICENSE diff --git a/.pipelines/stages/jobs/steps/utils/download-ort.yml b/.pipelines/stages/jobs/steps/utils/download-ort.yml index aa22b3974..366e3009e 100644 --- a/.pipelines/stages/jobs/steps/utils/download-ort.yml +++ b/.pipelines/stages/jobs/steps/utils/download-ort.yml @@ -1,6 +1,4 @@ parameters: -- name: genai_src - type: string - name: archiveType type: string steps: @@ -16,27 +14,27 @@ steps: userRepository: 'microsoft/onnxruntime' defaultVersionType: 'specificTag' version: 'v$(ort_version)' - itemPattern: '$(ort_filename)*' - downloadPath: '${{ parameters.genai_src }}' + itemPattern: '$(ort_filename).${{ parameters.archiveType }}' + downloadPath: '$(Build.Repository.LocalPath)' displayName: Download $(ort_filename) - task: ExtractFiles@1 inputs: - archiveFilePatterns: '**/*.${{ parameters.archiveType }}' - destinationFolder: '${{ parameters.genai_src }}' + archiveFilePatterns: '$(Build.Repository.LocalPath)/$(ort_filename).${{ parameters.archiveType }}' + destinationFolder: '$(Build.Repository.LocalPath)' cleanDestinationFolder: false overwriteExistingFiles: true displayName: Unzip OnnxRuntime - task: CopyFiles@2 inputs: - SourceFolder: '${{ parameters.genai_src }}/$(ort_filename)' - TargetFolder: '${{ parameters.genai_src }}/ort' + SourceFolder: '$(Build.Repository.LocalPath)/$(ort_filename)' + TargetFolder: '$(Build.Repository.LocalPath)/ort' displayName: Copy OnnxRuntime to ort - task: DeleteFiles@1 inputs: - SourceFolder: '${{ parameters.genai_src }}/ort/lib' + SourceFolder: '$(Build.Repository.LocalPath)/ort/lib' Contents: '*tensorrt*' RemoveSourceFolder: false displayName: 'Remove tensorrt from lib' diff --git a/CMakeLists.txt b/CMakeLists.txt index 93d95809e..280a6148d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,18 +31,7 @@ if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_ message(FATAL_ERROR "GCC version must be greater than or equal to 8") endif() -if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 10) - add_compile_definitions(USE_CXX17=1) - message("USE C++17") - set(CMAKE_CXX_STANDARD 17) -elseif(USE_CUDA AND CMAKE_CUDA_COMPILER AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12) - add_compile_definitions(USE_CXX17=1) - message("USE C++17 Because of CUDA Version is less than 12") - set(CMAKE_CXX_STANDARD 17) -else() - message("USE C++20") - set(CMAKE_CXX_STANDARD 20) -endif() +include(cmake/cxx_standard.cmake) set(GENERATORS_ROOT ${PROJECT_SOURCE_DIR}/src) set(MODELS_ROOT ${PROJECT_SOURCE_DIR}/src/models) @@ -139,11 +128,6 @@ else() target_include_directories(onnxruntime-genai-static PUBLIC ${TOKENIZER_ROOT}) target_link_libraries(onnxruntime-genai PRIVATE tokenizer) target_link_libraries(onnxruntime-genai-static PUBLIC tokenizer) - # if(NOT WIN32 AND USE_CXX17) - # target_link_libraries(onnxruntime-genai PRIVATE stdc++fs) - # target_link_libraries(onnxruntime-genai-static PRIVATE stdc++fs) - # message("Linking filesystem") - # endif() endif() if(ENABLE_TESTS) @@ -174,7 +158,7 @@ if(USE_CUDA AND CMAKE_CUDA_COMPILER) set_target_properties(onnxruntime-genai PROPERTIES LINKER_LANGUAGE CUDA) target_link_libraries(onnxruntime-genai PRIVATE cublasLt cublas curand cufft cudart) # onnxruntime-genai-static is statically linked under Windows - if(NOT WIN32) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set_target_properties(onnxruntime-genai-static PROPERTIES LINKER_LANGUAGE CUDA) target_link_libraries(onnxruntime-genai-static PRIVATE cublasLt cublas curand cufft cudart) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 10efbd2f1..a95bb33d2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -7,7 +7,9 @@ }, "include": [ "cmake/presets/CMakeWinBuildPresets.json", - "cmake/presets/CMakeLinuxBuildPresets.json" + "cmake/presets/CMakeLinuxBuildPresets.json", + "cmake/presets/CMakeMacOSBuildPresets.json" + ] } \ No newline at end of file diff --git a/cmake/cxx_standard.cmake b/cmake/cxx_standard.cmake new file mode 100644 index 000000000..7e752d40b --- /dev/null +++ b/cmake/cxx_standard.cmake @@ -0,0 +1,12 @@ +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 10) + add_compile_definitions(USE_CXX17=1) + message("Test is using C++17 because GCC Version is less than 10") + set(CMAKE_CXX_STANDARD 17) +elseif (USE_CUDA AND CMAKE_CUDA_COMPILER AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12) + add_compile_definitions(USE_CXX17=1) + message("Test is using C++17 Because CUDA Version is less than 12") + set(CMAKE_CXX_STANDARD 17) +else () + message("Test is using C++20") + set(CMAKE_CXX_STANDARD 20) +endif () \ No newline at end of file diff --git a/cmake/presets/CMakeLinuxClangConfigPresets.json b/cmake/presets/CMakeLinuxClangConfigPresets.json index 59c10d88a..ce607d2f1 100644 --- a/cmake/presets/CMakeLinuxClangConfigPresets.json +++ b/cmake/presets/CMakeLinuxClangConfigPresets.json @@ -11,7 +11,7 @@ "linux_clang_asan_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/release" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_clang_cpu_debug_asan", @@ -20,7 +20,7 @@ "linux_clang_asan_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/debug", + "binaryDir": "${sourceDir}/build/cpu", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_C_FLAGS": "-ggdb3 -O0 -fsanitize=address", @@ -34,7 +34,7 @@ "linux_clang_asan_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/relwithdebinfo" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_clang_cpu_minsizerel_asan", @@ -43,7 +43,7 @@ "linux_clang_default", "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/minsizerel", + "binaryDir": "${sourceDir}/build/cpu", "cacheVariables": {} }, { @@ -53,7 +53,7 @@ "linux_clang_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/release" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_clang_cpu_debug", @@ -62,7 +62,7 @@ "linux_clang_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/debug" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_clang_cpu_relwithdebinfo", @@ -71,7 +71,7 @@ "linux_clang_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/relwithdebinfo" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_clang_cpu_minsizerel", @@ -80,7 +80,7 @@ "linux_clang_default", "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/clang_cpu/minsizerel", + "binaryDir": "${sourceDir}/build/cpu", "cacheVariables": {} } ] diff --git a/cmake/presets/CMakeLinuxDefaultConfigPresets.json b/cmake/presets/CMakeLinuxDefaultConfigPresets.json index 9e904dc10..559d1dae0 100644 --- a/cmake/presets/CMakeLinuxDefaultConfigPresets.json +++ b/cmake/presets/CMakeLinuxDefaultConfigPresets.json @@ -39,6 +39,7 @@ }, { "name": "linux_gcc_asan_default", + "inherits": "linux_gcc_default", "cacheVariables": { "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address", "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address", @@ -47,6 +48,7 @@ }, { "name": "linux_clang_asan_default", + "inherits": "linux_clang_default", "cacheVariables": { "CMAKE_EXE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address -L\\usr\\lib64\\x86_64-unknown-linux-gnu", "CMAKE_MODULE_LINKER_FLAGS_INIT": "-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -fsanitize=address -L\\usr\\lib64\\x86_64-unknown-linux-gnu", diff --git a/cmake/presets/CMakeLinuxGccConfigPresets.json b/cmake/presets/CMakeLinuxGccConfigPresets.json index 4e7b45a4a..d5518f9ad 100644 --- a/cmake/presets/CMakeLinuxGccConfigPresets.json +++ b/cmake/presets/CMakeLinuxGccConfigPresets.json @@ -12,7 +12,7 @@ "linux_gcc_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/release" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_debug_asan", @@ -22,7 +22,7 @@ "linux_gcc_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/debug" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_relwithdebinfo_asan", @@ -32,7 +32,7 @@ "linux_gcc_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/relwithdebinfo" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_minsizerel_asan", @@ -42,7 +42,7 @@ "linux_gcc_default", "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/minsizerel" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_release", @@ -51,7 +51,7 @@ "linux_gcc_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/release" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_debug", @@ -60,7 +60,7 @@ "linux_gcc_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/debug" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_relwithdebinfo", @@ -69,7 +69,7 @@ "linux_gcc_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/relwithdebinfo" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cpu_minsizerel", @@ -78,7 +78,7 @@ "linux_gcc_default", "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/gcc_cpu/minsizerel" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "linux_gcc_cuda_release_asan", @@ -88,7 +88,7 @@ "linux_gcc_cuda_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/release" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_debug_asan", @@ -98,7 +98,7 @@ "linux_gcc_cuda_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/debug" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_relwithdebinfo_asan", @@ -108,7 +108,7 @@ "linux_gcc_cuda_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/relwithdebinfo" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_minsizerel_asan", @@ -118,7 +118,7 @@ "linux_gcc_cuda_default", "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/minsizerel" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_release", @@ -127,7 +127,7 @@ "linux_gcc_cuda_default", "linux_release_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/release" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_debug", @@ -136,7 +136,7 @@ "linux_gcc_cuda_default", "linux_debug_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/debug" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_relwithdebinfo", @@ -145,7 +145,7 @@ "linux_gcc_cuda_default", "linux_relwithdebinfo_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/relwithdebinfo" + "binaryDir": "${sourceDir}/build/cuda" }, { "name": "linux_gcc_cuda_minsizerel", @@ -154,7 +154,7 @@ "linux_gcc_cuda_default", "linux_minsizerel_default" ], - "binaryDir": "${sourceDir}/build/gcc_cuda/minsizerel" + "binaryDir": "${sourceDir}/build/cuda" } ] } \ No newline at end of file diff --git a/cmake/presets/CMakeMacOSBuildPresets.json b/cmake/presets/CMakeMacOSBuildPresets.json new file mode 100644 index 000000000..e27635e9c --- /dev/null +++ b/cmake/presets/CMakeMacOSBuildPresets.json @@ -0,0 +1,24 @@ +{ + "version": 6, + "include": [ + "CMakeMacOSConfigPresets.json" + ], + "buildPresets": [ + { + "name": "macos_cpu_release", + "configurePreset": "macos_cpu_release" + }, + { + "name": "macos_cpu_debug", + "configurePreset": "macos_cpu_debug" + }, + { + "name": "macos_cpu_relwithdebinfo", + "configurePreset": "macos_cpu_relwithdebinfo" + }, + { + "name": "macos_cpu_minsizerel", + "configurePreset": "macos_cpu_minsizerel" + } + ] +} \ No newline at end of file diff --git a/cmake/presets/CMakeMacOSConfigPresets.json b/cmake/presets/CMakeMacOSConfigPresets.json new file mode 100644 index 000000000..cd0c0a0b9 --- /dev/null +++ b/cmake/presets/CMakeMacOSConfigPresets.json @@ -0,0 +1,91 @@ +{ + "version": 6, + "include": [ + "CMakeLinuxDefaultConfigPresets.json" + ], + "configurePresets": [ + { + "name": "macos_default", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/cpu", + "cacheVariables": { + "CMAKE_POSITION_INDEPENDENT_CODE": "ON", + "CMAKE_OSX_ARCHITECTURES": "arm64", + "USE_CUDA": "OFF" + }, + "environment": { + "CC": "clang", + "CXX": "clang++" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "macos_release_default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_FLAGS": " -O3 -pipe", + "CMAKE_CXX_FLAGS": " -O3 -pipe" + } + }, + { + "name": "macos_relwithdebinfo_default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_C_FLAGS": "-O3 -pipe -ggdb3", + "CMAKE_CXX_FLAGS": "-O3 -pipe -ggdb3" + } + }, + { + "name": "macos_debug_default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_FLAGS": "-ggdb3 -O0", + "CMAKE_CXX_FLAGS": "-ggdb3 -O0 -D_GLIBCXX_DEBUG" + } + }, + { + "name": "macos_minsizerel_default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "MinSizeRel", + "CMAKE_C_FLAGS": "-Os -pipe -ggdb3", + "CMAKE_CXX_FLAGS": "-Os -pipe -ggdb3" + } + }, + { + "name": "macos_cpu_release", + "displayName": "macos cpu release", + "inherits": [ + "macos_default", + "macos_release_default" + ] + }, + { + "name": "macos_cpu_debug", + "displayName": "macos cpu debug", + "inherits": [ + "macos_default", + "macos_debug_default" + ] + }, + { + "name": "macos_cpu_relwithdebinfo", + "displayName": "macos cpu relwithdebinfo", + "inherits": [ + "macos_default", + "macos_relwithdebinfo_default" + ] + }, + { + "name": "macos_cpu_minsizerel", + "displayName": "macos cpu minsizerel", + "inherits": [ + "macos_default", + "macos_minsizerel_default" + ] + } + ] +} \ No newline at end of file diff --git a/cmake/presets/CMakeWinBuildPresets.json b/cmake/presets/CMakeWinBuildPresets.json index b42eec934..1edfd4e13 100644 --- a/cmake/presets/CMakeWinBuildPresets.json +++ b/cmake/presets/CMakeWinBuildPresets.json @@ -16,12 +16,12 @@ }, { "name": "windows_x64_cpu_relwithdebinfo_asan", - "configuration": "Relwithdebinfo", + "configuration": "RelWithDebInfo", "configurePreset": "windows_x64_cpu_relwithdebinfo_asan" }, { "name": "windows_x64_cpu_minsizerel_asan", - "configuration": "Minsizerel", + "configuration": "MinSizeRel", "configurePreset": "windows_x64_cpu_minsizerel_asan" }, { @@ -36,12 +36,12 @@ }, { "name": "windows_x64_cpu_relwithdebinfo", - "configuration": "Relwithdebinfo", + "configuration": "RelWithDebInfo", "configurePreset": "windows_x64_cpu_relwithdebinfo" }, { "name": "windows_x64_cpu_minsizerel", - "configuration": "Minsizerel", + "configuration": "MinSizeRel", "configurePreset": "windows_x64_cpu_minsizerel" }, { @@ -56,12 +56,12 @@ }, { "name": "windows_x64_cuda_relwithdebinfo_asan", - "configuration": "Relwithdebinfo", + "configuration": "RelWithDebInfo", "configurePreset": "windows_x64_cuda_relwithdebinfo_asan" }, { "name": "windows_x64_cuda_minsizerel_asan", - "configuration": "Minsizerel", + "configuration": "MinSizeRel", "configurePreset": "windows_x64_cuda_minsizerel_asan" }, { @@ -76,17 +76,17 @@ }, { "name": "windows_x64_cuda_relwithdebinfo", - "configuration": "Relwithdebinfo", + "configuration": "RelWithDebInfo", "configurePreset": "windows_x64_cuda_relwithdebinfo" }, { "name": "windows_x64_cuda_minsizerel", - "configuration": "Minsizerel", + "configuration": "MinSizeRel", "configurePreset": "windows_x64_cuda_minsizerel" }, { "name": "windows_arm64_cpu_relwithdebinfo", - "configuration": "Relwithdebinfo", + "configuration": "RelWithDebInfo", "configurePreset": "windows_arm64_cpu_relwithdebinfo" }, { diff --git a/cmake/presets/CMakeWinConfigPresets.json b/cmake/presets/CMakeWinConfigPresets.json index be70ca2d6..3b22aae07 100644 --- a/cmake/presets/CMakeWinConfigPresets.json +++ b/cmake/presets/CMakeWinConfigPresets.json @@ -28,6 +28,7 @@ { "name": "windows_release_default", "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG" } @@ -35,6 +36,7 @@ { "name": "windows_debug_default", "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1" } @@ -42,6 +44,7 @@ { "name": "windows_relwithdebinfo_default", "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG" } @@ -49,12 +52,14 @@ { "name": "windows_minsizerel_default", "cacheVariables": { + "CMAKE_BUILD_TYPE": "MinSizeRel", "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG" } }, { "name": "windows_release_asan_default", + "inherits": "windows_release_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob2 /DNDEBUG /fsanitize=address" @@ -62,6 +67,7 @@ }, { "name": "windows_debug_asan_default", + "inherits": "windows_debug_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1 /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /Ob0 /Od /RTC1 /fsanitize=address" @@ -69,6 +75,7 @@ }, { "name": "windows_relwithdebinfo_asan_default", + "inherits": "windows_relwithdebinfo_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O2 /Ob1 /DNDEBUG /fsanitize=address" @@ -76,6 +83,7 @@ }, { "name": "windows_minsizerel_asan_default", + "inherits": "windows_minsizerel_default", "cacheVariables": { "CMAKE_C_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG /fsanitize=address", "CMAKE_CXX_FLAGS": "/EHsc /Qspectre /MP /guard:cf /DWIN32 /D_WINDOWS /DWINAPI_FAMILY=100 /DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00 /DNTDDI_VERSION=0x0A000000 /O1 /Ob1 /DNDEBUG /fsanitize=address" @@ -88,7 +96,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu release asan", - "binaryDir": "${sourceDir}/build/release/cpu_asan" + "binaryDir": "${sourceDir}/build/cpu_asan" }, { "name": "windows_x64_cpu_debug_asan", @@ -97,7 +105,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu debug asan", - "binaryDir": "${sourceDir}/build/debug/cpu_asan" + "binaryDir": "${sourceDir}/build/cpu_asan" }, { "name": "windows_x64_cpu_relwithdebinfo_asan", @@ -106,7 +114,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu relwithdebinfo asan", - "binaryDir": "${sourceDir}/build/relwithdebinfo/cpu_asan" + "binaryDir": "${sourceDir}/build/cpu_asan" }, { "name": "windows_x64_cpu_minsizerel_asan", @@ -115,7 +123,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu minsizerel asan", - "binaryDir": "${sourceDir}/build/minsizerel/cpu_asan" + "binaryDir": "${sourceDir}/build/cpu_asan" }, { "name": "windows_x64_cpu_release", @@ -124,7 +132,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu release", - "binaryDir": "${sourceDir}/build/release/cpu_default" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "windows_x64_cpu_debug", @@ -133,7 +141,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu debug", - "binaryDir": "${sourceDir}/build/debug/cpu_default" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "windows_x64_cpu_relwithdebinfo", @@ -142,7 +150,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu relwithdebinfo", - "binaryDir": "${sourceDir}/build/relwithdebinfo/cpu_default" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "windows_x64_cpu_minsizerel", @@ -151,7 +159,7 @@ "windows_cpu_default" ], "displayName": "windows x64 cpu minsizerel", - "binaryDir": "${sourceDir}/build/minsizerel/cpu_default" + "binaryDir": "${sourceDir}/build/cpu" }, { "name": "windows_x64_cuda_release_asan", @@ -160,7 +168,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda release asan", - "binaryDir": "${sourceDir}/build/release/cuda_asan", + "binaryDir": "${sourceDir}/build/cuda_asan", "cacheVariables": { "USE_CUDA": "ON" } @@ -172,7 +180,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda debug asan", - "binaryDir": "${sourceDir}/build/debug/cuda_asan", + "binaryDir": "${sourceDir}/build/cuda_asan", "cacheVariables": { "USE_CUDA": "ON" } @@ -184,7 +192,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda relwithdebinfo asan", - "binaryDir": "${sourceDir}/build/relwithdebinfo/cuda_asan", + "binaryDir": "${sourceDir}/build/cuda_asan", "cacheVariables": { "USE_CUDA": "ON" } @@ -196,7 +204,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda minsizerel asan", - "binaryDir": "${sourceDir}/build/minsizerel/cuda_asan", + "binaryDir": "${sourceDir}/build/cuda_asan", "cacheVariables": { "USE_CUDA": "ON" } @@ -208,7 +216,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda release", - "binaryDir": "${sourceDir}/build/release/cuda_default", + "binaryDir": "${sourceDir}/build/cuda", "cacheVariables": { "USE_CUDA": "ON" } @@ -220,7 +228,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda debug", - "binaryDir": "${sourceDir}/build/debug/cuda_default", + "binaryDir": "${sourceDir}/build/cuda", "cacheVariables": { "USE_CUDA": "ON" } @@ -232,7 +240,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda relwithdebinfo", - "binaryDir": "${sourceDir}/build/relwithdebinfo/cuda_default", + "binaryDir": "${sourceDir}/build/cuda", "cacheVariables": { "USE_CUDA": "ON" } @@ -244,7 +252,7 @@ "windows_cuda_default" ], "displayName": "windows x64 cuda minsizerel", - "binaryDir": "${sourceDir}/build/minsizerel/cuda_default", + "binaryDir": "${sourceDir}/build/cuda", "cacheVariables": { "USE_CUDA": "ON" } diff --git a/src/json.cpp b/src/json.cpp index 8f3c4c88f..412b98509 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -202,7 +202,7 @@ void JSON::Parse_Array(Element& element) { double JSON::Parse_Number() { double value = NAN; -#ifndef USE_CXX17 +#if !defined(USE_CXX17) && !defined(__APPLE__) auto result = std::from_chars(current_, end_, value); if (result.ec != std::errc{}) { throw std::runtime_error("Expecting number"); diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index a991a6167..942664246 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -1,15 +1,4 @@ -if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 10) - add_compile_definitions(USE_CXX17=1) - message("Python is using C++17 because GCC Version is less than 10") - set(CMAKE_CXX_STANDARD 17) -elseif(USE_CUDA AND CMAKE_CUDA_COMPILER AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12) - add_compile_definitions(USE_CXX17=1) - message("Python is using C++17 Because CUDA Version is less than 12") - set(CMAKE_CXX_STANDARD 17) -else() - message("Python is using C++20") - set(CMAKE_CXX_STANDARD 20) -endif() +include(${CMAKE_SOURCE_DIR}/cmake/cxx_standard.cmake) file(GLOB python_srcs CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.h" @@ -19,8 +8,9 @@ pybind11_add_module(python ${python_srcs}) target_include_directories(python PRIVATE ${ORT_HEADER_DIR}) target_link_directories(python PRIVATE ${ORT_LIB_DIR}) target_link_libraries(python PRIVATE onnxruntime-genai-static ${ONNXRUNTIME_LIB}) -if(NOT WIN32) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set_property(TARGET python APPEND_STRING PROPERTY LINK_FLAGS " -Xlinker -rpath=\\$ORIGIN") + target_link_libraries(python PRIVATE stdc++fs) endif() set_target_properties(python PROPERTIES OUTPUT_NAME "onnxruntime_genai") @@ -29,10 +19,6 @@ if(CMAKE_GENERATOR_TOOLSET MATCHES "Visual Studio") target_compile_options(python PRIVATE "/sdl" PRIVATE "/Qspectre") endif() -if(NOT WIN32) - target_link_libraries(python PRIVATE stdc++fs) -endif() - if(USE_CUDA AND CMAKE_CUDA_COMPILER) cmake_policy(SET CMP0104 OLD) enable_language(CUDA) diff --git a/src/tokenizer/CMakeLists.txt b/src/tokenizer/CMakeLists.txt index 8165a20f0..69d603715 100644 --- a/src/tokenizer/CMakeLists.txt +++ b/src/tokenizer/CMakeLists.txt @@ -1,17 +1,5 @@ set(TOKENIZER_ROOT ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) - -if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 10) - add_compile_definitions(USE_CXX17=1) - message("Tokenizer is using C++17 because GCC Version is less than 10") - set(CMAKE_CXX_STANDARD 17) -elseif(USE_CUDA AND CMAKE_CUDA_COMPILER AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12) - add_compile_definitions(USE_CXX17=1) - message("Tokenizer is using C++17 Because CUDA Version is less than 12") - set(CMAKE_CXX_STANDARD 17) -else() - message("Tokenizer is using C++20") - set(CMAKE_CXX_STANDARD 20) -endif() +include(${CMAKE_SOURCE_DIR}/cmake/cxx_standard.cmake) file(GLOB tokenizer_srcs CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cc" @@ -27,10 +15,6 @@ FetchContent_MakeAvailable(simdjson) add_library(tokenizer STATIC ${tokenizer_srcs}) -if(NOT WIN32) - target_link_libraries(tokenizer PRIVATE stdc++fs) -endif() - message(STATUS "GSL_SOURCE_DIR: ${GSL_SOURCE_DIR}") target_include_directories(tokenizer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} @@ -40,6 +24,7 @@ target_include_directories(tokenizer PUBLIC target_compile_definitions(tokenizer PRIVATE _SILENCE_ALL_CXX20_DEPRECATION_WARNINGS) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_libraries(tokenizer PRIVATE stdc++fs) set_target_properties(tokenizer PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(simdjson PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fb3d424cd..83571118e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,17 +1,6 @@ enable_testing() -if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 10) - add_compile_definitions(USE_CXX17=1) - message("Test is using C++17 because GCC Version is less than 10") - set(CMAKE_CXX_STANDARD 17) -elseif(USE_CUDA AND CMAKE_CUDA_COMPILER AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 12) - add_compile_definitions(USE_CXX17=1) - message("Test is using C++17 Because CUDA Version is less than 12") - set(CMAKE_CXX_STANDARD 17) -else() - message("Test is using C++20") - set(CMAKE_CXX_STANDARD 20) -endif() +include(${CMAKE_SOURCE_DIR}/cmake/cxx_standard.cmake) set(TESTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) file(GLOB test_srcs CONFIGURE_DEPENDS @@ -40,7 +29,7 @@ target_link_libraries(unit_tests PRIVATE GTest::gtest_main ) -if(NOT WIN32) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(unit_tests PRIVATE stdc++fs) endif() From 551f253e427fa11cbdae7615dc84bcf8624331fd Mon Sep 17 00:00:00 2001 From: Jian Chen Date: Wed, 27 Mar 2024 10:22:03 -0700 Subject: [PATCH 23/23] pip install onnxruntime_genai-*.whl --- examples/python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/python/README.md b/examples/python/README.md index 513132fee..6d1490de2 100644 --- a/examples/python/README.md +++ b/examples/python/README.md @@ -6,7 +6,7 @@ Install the python package according to the [installation instructions](https:// ```bash cd build/wheel - pip install onnxruntime-genai-*.whl + pip install onnxruntime_genai-*.whl ``` ## Get the model